From 2dd8be4d8e67c00b409fbcd931ebe43f8b0c3b7f Mon Sep 17 00:00:00 2001 From: Luke Rodgers Date: Wed, 17 Oct 2018 21:54:44 +0100 Subject: [PATCH 001/841] Throw exception to prevent infinite loop caused by third party code. Third party code combined with `trigger_recollect` can cause infinite loops in some scenarios. The fix needs to be applied to the third party code, but the Magento core could be more explicit with these kinds of errors. --- app/code/Magento/Checkout/Model/Session.php | 14 ++ .../Model/Config.php | 29 ++++ .../Observer/AfterCollectTotals.php | 47 ++++++ .../etc/events.xml | 12 ++ .../etc/module.xml | 11 ++ .../registration.php | 12 ++ .../Quote/Model/QuoteInfiniteLoopTest.php | 138 ++++++++++++++++++ 7 files changed, 263 insertions(+) create mode 100644 dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php create mode 100644 dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php create mode 100644 dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/events.xml create mode 100644 dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/module.xml create mode 100644 dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/registration.php create mode 100644 dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php diff --git a/app/code/Magento/Checkout/Model/Session.php b/app/code/Magento/Checkout/Model/Session.php index 31513d25a9ce1..571467cbc9571 100644 --- a/app/code/Magento/Checkout/Model/Session.php +++ b/app/code/Magento/Checkout/Model/Session.php @@ -41,6 +41,15 @@ class Session extends \Magento\Framework\Session\SessionManager */ protected $_loadInactive = false; + /** + * A flag to track when the quote is being loaded and attached to the session object. + * + * Used in trigger_recollect infinite loop detection. + * + * @var bool + */ + protected $_isLoading = false; + /** * Loaded order instance * @@ -210,6 +219,10 @@ public function getQuote() $this->_eventManager->dispatch('custom_quote_process', ['checkout_session' => $this]); if ($this->_quote === null) { + if ($this->_isLoading) { + throw new \LogicException("Infinite loop detected, review the trace for the looping path"); + } + $this->_isLoading = true; $quote = $this->quoteFactory->create(); if ($this->getQuoteId()) { try { @@ -262,6 +275,7 @@ public function getQuote() $quote->setStore($this->_storeManager->getStore()); $this->_quote = $quote; + $this->_isLoading = false; } if (!$this->isQuoteMasked() && !$this->_customerSession->isLoggedIn() && $this->getQuoteId()) { diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php new file mode 100644 index 0000000000000..74d9d51278814 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php @@ -0,0 +1,29 @@ +active = true; + } + + public function disableObserver() + { + $this->active = false; + } + + public function isActive() + { + return $this->active; + } +} + diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php new file mode 100644 index 0000000000000..9392239891c21 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php @@ -0,0 +1,47 @@ +config = $config; + $this->session = $messageManager; + } + + /** + * @param Observer $observer + * @return void + */ + public function execute(\Magento\Framework\Event\Observer $observer) + { + if ($this->config->isActive()) { + $this->session->getQuote(); + } + } +} + diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/events.xml b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/events.xml new file mode 100644 index 0000000000000..91dd450e1934f --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/events.xml @@ -0,0 +1,12 @@ + + + + + + + diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/module.xml b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/module.xml new file mode 100644 index 0000000000000..a24489ba74173 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/module.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/registration.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/registration.php new file mode 100644 index 0000000000000..61e2553951b62 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/registration.php @@ -0,0 +1,12 @@ +getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleQuoteTotalsObserver') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleQuoteTotalsObserver', __DIR__); +} diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php new file mode 100644 index 0000000000000..0982858320e48 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php @@ -0,0 +1,138 @@ +objectManager = Bootstrap::getObjectManager(); + $this->config = $this->objectManager->get(\Magento\TestModuleQuoteTotalsObserver\Model\Config::class); + $this->config->disableObserver(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->config->disableObserver(); + $this->objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class); + } + + /** + * @dataProvider getLoadQuoteParametersProvider + * + * @param $triggerRecollect + * @param $thirdPartObserverEnabled + * @return void + */ + public function testLoadQuoteSuccessfully($triggerRecollect, $thirdPartObserverEnabled): void + { + $originalQuote = $this->generateQuote($triggerRecollect); + $quoteId = $originalQuote->getId(); + + $this->assertGreaterThan(0, $quoteId, "The quote should have a database id"); + $this->assertEquals( + $triggerRecollect, + $originalQuote->getTriggerRecollect(), + "trigger_recollect failed to be set" + ); + + if ($thirdPartObserverEnabled) { + $this->config->enableObserver(); + } + + /** @var $session \Magento\Checkout\Model\Session */ + $this->objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class); + $session = $this->objectManager->get(\Magento\Checkout\Model\Session::class); + $session->setQuoteId($quoteId); + + $quote = $session->getQuote(); + $this->assertEquals($quoteId, $quote->getId(), "The loaded quote should have the same ID as the initial quote"); + $this->assertEquals(0, $quote->getTriggerRecollect(), "trigger_recollect should be unset after a quote reload"); + } + + /** + * @return array + */ + public function getLoadQuoteParametersProvider() + { + return [ + [0, false], + [0, true], + [1, false], + //[1, true], this combination of trigger recollect and third party code causes the loop, tested separately + ]; + } + + /** + * @expectedException \LogicException + * @expectedExceptionMessage Infinite loop detected, review the trace for the looping path + * + * @return void + */ + public function testLoadQuoteWithTriggerRecollectInfiniteLoop(): void + { + $originalQuote = $this->generateQuote(); + $quoteId = $originalQuote->getId(); + + $this->assertGreaterThan(0, $quoteId, "The quote should have a database id"); + $this->assertEquals(1, $originalQuote->getTriggerRecollect(), "The quote has trigger_recollect set"); + + // Enable an observer which gets the quote from the session + // The observer hooks into part of the collect totals process for an easy demonstration of the loop. + $this->config->enableObserver(); + + /** @var $session \Magento\Checkout\Model\Session */ + $this->objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class); + $session = $this->objectManager->get(\Magento\Checkout\Model\Session::class); + $session->setQuoteId($quoteId); + $session->getQuote(); + } + + /** + * Generate a quote with trigger_recollect and save it in the database. + * + * @param int $triggerRecollect + * @return Quote + */ + private function generateQuote($triggerRecollect = 1) + { + //Fully init a quote with standard quote session procedure + /** @var $session \Magento\Checkout\Model\Session */ + $session = $this->objectManager->create(\Magento\Checkout\Model\Session::class); + $session->setQuoteId(null); + $quote = $session->getQuote(); + $quote->setTriggerRecollect($triggerRecollect); + + /** @var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->create('\Magento\Quote\Api\CartRepositoryInterface'); + $quoteRepository->save($quote); + return $quote; + } +} From 8be25bfb952cf47be97a484d45301699f01ef0f2 Mon Sep 17 00:00:00 2001 From: Luke Rodgers Date: Thu, 15 Nov 2018 13:35:26 +0000 Subject: [PATCH 002/841] Fix codacy failures --- .../Observer/AfterCollectTotals.php | 1 + .../testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php index 9392239891c21..02e3b42686d22 100644 --- a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php @@ -39,6 +39,7 @@ public function __construct(\Magento\Checkout\Model\Session $messageManager, \Ma */ public function execute(\Magento\Framework\Event\Observer $observer) { + $observer->getEvent(); if ($this->config->isActive()) { $this->session->getQuote(); } diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php index 0982858320e48..2b0bc8f4d1d05 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php @@ -48,10 +48,10 @@ protected function tearDown() * @dataProvider getLoadQuoteParametersProvider * * @param $triggerRecollect - * @param $thirdPartObserverEnabled + * @param $observerEnabled * @return void */ - public function testLoadQuoteSuccessfully($triggerRecollect, $thirdPartObserverEnabled): void + public function testLoadQuoteSuccessfully($triggerRecollect, $observerEnabled): void { $originalQuote = $this->generateQuote($triggerRecollect); $quoteId = $originalQuote->getId(); @@ -63,7 +63,7 @@ public function testLoadQuoteSuccessfully($triggerRecollect, $thirdPartObserverE "trigger_recollect failed to be set" ); - if ($thirdPartObserverEnabled) { + if ($observerEnabled) { $this->config->enableObserver(); } From 5a24c411060934c8496830762704e5d07131a80a Mon Sep 17 00:00:00 2001 From: Andriy Kravets Date: Thu, 24 Jan 2019 13:39:29 +0200 Subject: [PATCH 003/841] magento/magento2#19230: I Can't Cancel Order. --- .../Sales/Model/Order/CustomerAssignment.php | 59 ++++ .../AssignOrderToCustomerObserver.php | 23 +- .../AssignOrderToCustomerObserverTest.php | 20 +- ...onDataAfterOrderCustomerAssignObserver.php | 52 ++++ app/code/Magento/SalesRule/etc/events.xml | 3 + ...CouponDataAfterOrderCustomerAssignTest.php | 291 ++++++++++++++++++ 6 files changed, 436 insertions(+), 12 deletions(-) create mode 100644 app/code/Magento/Sales/Model/Order/CustomerAssignment.php create mode 100644 app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php diff --git a/app/code/Magento/Sales/Model/Order/CustomerAssignment.php b/app/code/Magento/Sales/Model/Order/CustomerAssignment.php new file mode 100644 index 0000000000000..8bcfc1dc49de4 --- /dev/null +++ b/app/code/Magento/Sales/Model/Order/CustomerAssignment.php @@ -0,0 +1,59 @@ +eventManager = $eventManager; + $this->orderRepository = $orderRepository; + } + + /** + * @param OrderInterface $order + * @param CustomerInterface $customer + */ + public function execute(OrderInterface $order, CustomerInterface $customer)/*: void*/ + { + $order->setCustomerId($customer->getId()); + $order->setCustomerIsGuest(false); + $this->orderRepository->save($order); + + $this->eventManager->dispatch( + 'sales_order_customer_assign_after', + [ + 'order' => $order, + 'customer' => $customer + ] + ); + } +} diff --git a/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php b/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php index cade86d18e935..7418f142d938a 100644 --- a/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php +++ b/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php @@ -11,6 +11,7 @@ use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\CustomerAssignment; /** * Assign order to customer created after issuing guest order. @@ -23,11 +24,22 @@ class AssignOrderToCustomerObserver implements ObserverInterface private $orderRepository; /** + * @var CustomerAssignment + */ + private $assignmentService; + + /** + * AssignOrderToCustomerObserver constructor. + * * @param OrderRepositoryInterface $orderRepository + * @param CustomerAssignment $assignmentService */ - public function __construct(OrderRepositoryInterface $orderRepository) - { + public function __construct( + OrderRepositoryInterface $orderRepository, + CustomerAssignment $assignmentService + ) { $this->orderRepository = $orderRepository; + $this->assignmentService = $assignmentService; } /** @@ -43,11 +55,8 @@ public function execute(Observer $observer) if (array_key_exists('__sales_assign_order_id', $delegateData)) { $orderId = $delegateData['__sales_assign_order_id']; $order = $this->orderRepository->get($orderId); - if (!$order->getCustomerId()) { - //if customer ID wasn't already assigned then assigning. - $order->setCustomerId($customer->getId()); - $order->setCustomerIsGuest(0); - $this->orderRepository->save($order); + if (!$order->getCustomerId() && $customer->getId()) { + $this->assignmentService->execute($order, $customer); } } } diff --git a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php index c6e02151b9bc1..18371274049e3 100644 --- a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php +++ b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php @@ -12,6 +12,7 @@ use Magento\Framework\Event\Observer; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\CustomerAssignment; use Magento\Sales\Observer\AssignOrderToCustomerObserver; use PHPUnit\Framework\TestCase; use PHPUnit_Framework_MockObject_MockObject; @@ -27,6 +28,9 @@ class AssignOrderToCustomerObserverTest extends TestCase /** @var OrderRepositoryInterface|PHPUnit_Framework_MockObject_MockObject */ protected $orderRepositoryMock; + /** @var CustomerAssignment | PHPUnit_Framework_MockObject_MockObject */ + protected $assignmentMock; + /** * Set Up */ @@ -35,7 +39,12 @@ protected function setUp() $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->sut = new AssignOrderToCustomerObserver($this->orderRepositoryMock); + + $this->assignmentMock = $this->getMockBuilder(CustomerAssignment::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->sut = new AssignOrderToCustomerObserver($this->orderRepositoryMock, $this->assignmentMock); } /** @@ -69,13 +78,14 @@ public function testAssignOrderToCustomerAfterGuestOrder($customerId) $orderMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); $this->orderRepositoryMock->expects($this->once())->method('get')->with($orderId) ->willReturn($orderMock); - if (!$customerId) { - $this->orderRepositoryMock->expects($this->once())->method('save')->with($orderMock); + + if ($customerId) { + $this->assignmentMock->expects($this->once())->method('execute')->with($orderMock, $customerMock); $this->sut->execute($observerMock); - return ; + return; } - $this->orderRepositoryMock->expects($this->never())->method('save')->with($orderMock); + $this->assignmentMock->expects($this->never())->method('execute'); $this->sut->execute($observerMock); } diff --git a/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php new file mode 100644 index 0000000000000..d9699d334ff6a --- /dev/null +++ b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php @@ -0,0 +1,52 @@ +updateCouponUsages = $updateCouponUsages; + } + + /** + * @inheritDoc + */ + public function execute(Observer $observer) + { + $event = $observer->getEvent(); + /** @var OrderInterface $order */ + $order = $event->getData(self::EVENT_KEY_ORDER); + + if ($order->getCustomerId()) { + $this->updateCouponUsages->execute($order, true); + } + } +} diff --git a/app/code/Magento/SalesRule/etc/events.xml b/app/code/Magento/SalesRule/etc/events.xml index 43babc40a2ab5..a715348a4f65f 100644 --- a/app/code/Magento/SalesRule/etc/events.xml +++ b/app/code/Magento/SalesRule/etc/events.xml @@ -27,4 +27,7 @@ + + + diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php new file mode 100644 index 0000000000000..397650df416e9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php @@ -0,0 +1,291 @@ +objectManager = Bootstrap::getObjectManager(); + $this->eventManager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); + $this->orderRepository = $this->objectManager->get(\Magento\Sales\Model\OrderRepository::class); + $this->delegateCustomerService = $this->objectManager->get(Order\OrderCustomerDelegate::class); + $this->customerRepository = $this->objectManager->get(\Magento\Customer\Api\CustomerRepositoryInterface::class); + $this->ruleCustomerFactory = $this->objectManager->get(\Magento\SalesRule\Model\Rule\CustomerFactory::class); + $this->assignCouponToCustomerObserver = $this->objectManager->get( + \Magento\SalesRule\Observer\AssignCouponDataAfterOrderCustomerAssignObserver::class + ); + + $this->salesRule = $this->prepareSalesRule(); + $this->coupon = $this->attachSalesruleCoupon($this->salesRule); + $this->order = $this->makeOrderWithCouponAsGuest($this->coupon); + $this->delegateOrderToBeAssigned($this->order); + $this->customer = $this->registerNewCustomer(); + $this->order->setCustomerId($this->customer->getId()); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->salesRule = null; + $this->customer = null; + $this->coupon = null; + $this->order = null; + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testCouponDataHasBeenAssignedTest() + { + $ruleCustomer = $this->getSalesruleCustomerUsage($this->customer, $this->salesRule); + + // Assert, that rule customer model has been created for specific customer + $this->assertEquals( + $ruleCustomer->getCustomerId(), + $this->customer->getId() + ); + + // Assert, that customer has increased coupon usage of specific rule + $this->assertEquals( + 1, + $ruleCustomer->getTimesUsed() + ); + } + + /** + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testOrderCancelingDecreasesCouponUsages() + { + $this->processOrder($this->order); + + // Should not throw exception as bux is fixed now + $this->order->cancel(); + $ruleCustomer = $this->getSalesruleCustomerUsage($this->customer, $this->salesRule); + + // Assert, that rule customer model has been created for specific customer + $this->assertEquals( + $ruleCustomer->getCustomerId(), + $this->customer->getId() + ); + + // Assert, that customer has increased coupon usage of specific rule + $this->assertEquals( + 0, + $ruleCustomer->getTimesUsed() + ); + } + + /** + * @param Order $order + * @return \Magento\Sales\Api\Data\OrderInterface + */ + private function processOrder(Order $order) + { + $order->setState(Order::STATE_PROCESSING); + $order->setStatus(Order::STATE_PROCESSING); + return $this->orderRepository->save($order); + } + + /** + * @param Customer $customer + * @param Rule $rule + * @return Rule\Customer + */ + private function getSalesruleCustomerUsage(Customer $customer, Rule $rule) : \Magento\SalesRule\Model\Rule\Customer + { + $ruleCustomer = $this->ruleCustomerFactory->create(); + return $ruleCustomer->loadByCustomerRule($customer->getId(), $rule->getRuleId()); + } + + /** + * @return Rule + */ + private function prepareSalesRule() : Rule + { + /** @var Rule $salesRule */ + $salesRule = $this->objectManager->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' => [ + $this->objectManager->get(StoreManagerInterface::class)->getWebsite()->getId(), + ], + ] + ); + Bootstrap::getObjectManager()->get( + \Magento\SalesRule\Model\ResourceModel\Rule::class + )->save($salesRule); + + return $salesRule; + } + + /** + * @param Rule $salesRule + * @return Coupon + */ + private function attachSalesruleCoupon(Rule $salesRule) : Coupon + { + $coupon = $this->objectManager->create(Coupon::class); + $coupon->setRuleId($salesRule->getId()) + ->setCode('CART_FIXED_DISCOUNT_15') + ->setType(0); + + Bootstrap::getObjectManager()->get(CouponRepositoryInterface::class)->save($coupon); + + return $coupon; + } + + /** + * @param Coupon $coupon + * @return Order + */ + private function makeOrderWithCouponAsGuest(Coupon $coupon) : Order + { + $order = Bootstrap::getObjectManager()->create(\Magento\Sales\Model\Order::class); + $order->loadByIncrementId('100000001') + ->setCustomerIsGuest(true) + ->setCouponCode($coupon->getCode()) + ->setCreatedAt('2014-10-25 10:10:10') + ->setAppliedRuleIds($coupon->getRuleId()) + ->save(); + + return $order; + } + + /** + * @param Order $order + */ + private function delegateOrderToBeAssigned(Order $order) + { + $this->delegateCustomerService->delegateNew($order->getId()); + } + + /** + * @return Customer + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\State\InputMismatchException + */ + private function registerNewCustomer() : Customer + { + $customer = Bootstrap::getObjectManager()->create( + \Magento\Customer\Api\Data\CustomerInterface::class + ); + + /** @var Magento\Customer\Api\Data\CustomerInterface $customer */ + $customer->setWebsiteId(1) + ->setEmail('customer@example.com') + ->setGroupId(1) + ->setStoreId(1) + ->setPrefix('Mr.') + ->setFirstname('John') + ->setMiddlename('A') + ->setLastname('Smith') + ->setSuffix('Esq.') + ->setDefaultBilling(1) + ->setDefaultShipping(1) + ->setTaxvat('12') + ->setGender(0); + + $customer = $this->customerRepository->save($customer, 'password'); + + return $customer; + } +} From 44760ff282f67091c8eb325ce81c41655bdabc7a Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Thu, 24 Jan 2019 14:36:41 +0200 Subject: [PATCH 004/841] magento/magento2#19230: Static test fix. --- app/code/Magento/Sales/Model/Order/CustomerAssignment.php | 5 +++++ .../Magento/Sales/Observer/AssignOrderToCustomerObserver.php | 2 +- .../AssignCouponDataAfterOrderCustomerAssignObserver.php | 3 +++ .../AssignCouponDataAfterOrderCustomerAssignTest.php | 4 +++- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/CustomerAssignment.php b/app/code/Magento/Sales/Model/Order/CustomerAssignment.php index 8bcfc1dc49de4..eb4e790c311ca 100644 --- a/app/code/Magento/Sales/Model/Order/CustomerAssignment.php +++ b/app/code/Magento/Sales/Model/Order/CustomerAssignment.php @@ -12,6 +12,9 @@ use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\Event\ManagerInterface; +/** + * Assign customer to order. + */ class CustomerAssignment { /** @@ -39,6 +42,8 @@ public function __construct( } /** + * Assign customer to order. + * * @param OrderInterface $order * @param CustomerInterface $customer */ diff --git a/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php b/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php index 7418f142d938a..546e2f5e510e2 100644 --- a/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php +++ b/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php @@ -43,7 +43,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function execute(Observer $observer) { diff --git a/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php index d9699d334ff6a..4dadfb453f51e 100644 --- a/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php +++ b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php @@ -14,6 +14,9 @@ use Magento\Sales\Api\Data\OrderInterface; use Magento\Framework\Event\ObserverInterface; +/** + * Assign coupon data after order customer assign. + */ class AssignCouponDataAfterOrderCustomerAssignObserver implements ObserverInterface { const EVENT_KEY_CUSTOMER = 'customer'; diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php index 397650df416e9..0452666bdff44 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php @@ -6,6 +6,7 @@ namespace Magento\SalesRule\Model\Observer; +use Magento\Framework\Controller\Result\Redirect; use Magento\Sales\Model\Order; use Magento\Customer\Model\GroupManagement; use Magento\SalesRule\Api\CouponRepositoryInterface; @@ -251,8 +252,9 @@ private function makeOrderWithCouponAsGuest(Coupon $coupon) : Order /** * @param Order $order + * @return Redirect */ - private function delegateOrderToBeAssigned(Order $order) + private function delegateOrderToBeAssigned(Order $order): Redirect { $this->delegateCustomerService->delegateNew($order->getId()); } From 073121aaf7bc42e30f9ab19ce78ae3cb0c1c7281 Mon Sep 17 00:00:00 2001 From: Vasilii Date: Tue, 29 Jan 2019 10:51:09 +0200 Subject: [PATCH 005/841] Added massaction and delete option to admin widget instances page --- .../layout/adminhtml_widget_instance_block.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml index 934b0ca1a85b2..0d454efeb09eb 100644 --- a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml +++ b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml @@ -15,6 +15,20 @@ ASC Magento\Widget\Model\ResourceModel\Widget\Instance\Collection + + + instance_id + delete + 1 + + + Delete + */*/massDelete + 0 + + + + From 4376b91ff1fe7c63e1593c83e782be84891c3d42 Mon Sep 17 00:00:00 2001 From: Vasilii Date: Tue, 29 Jan 2019 12:31:23 +0200 Subject: [PATCH 006/841] Added possibility to mass-delete widget instances --- .../Api/DeleteWidgetInstanceByIdInterface.php | 23 ++++ .../Adminhtml/Widget/Instance/MassDelete.php | 116 ++++++++++++++++++ .../Widget/Model/DeleteWidgetInstanceById.php | 72 +++++++++++ app/code/Magento/Widget/etc/di.xml | 2 + 4 files changed, 213 insertions(+) create mode 100644 app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php create mode 100644 app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php create mode 100644 app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php diff --git a/app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php b/app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php new file mode 100644 index 0000000000000..3045912f7b0d2 --- /dev/null +++ b/app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php @@ -0,0 +1,23 @@ +deleteWidgetInstanceById = $deleteWidgetInstanceById; + } + + /** + * Execute action + * + * @return Redirect + */ + public function execute() + { + $deletedInstances = 0; + $notDeletedInstances = []; + /** @var array $instanceIds */ + $instanceIds = $this->getInstanceIds(); + + if (!count($instanceIds)) { + $this->messageManager->addErrorMessage(__('No widget instance IDs were provided to be deleted.')); + + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->getResultPage(); + + return $resultRedirect->setPath('*/*/'); + } + + foreach ($instanceIds as $key => $instanceId) { + try { + $this->deleteWidgetInstanceById->execute((int) $instanceId); + $deletedInstances++; + } catch (NoSuchEntityException $e) { + $notDeletedInstances[] = $instanceId; + } + } + + if ($deletedInstances) { + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $deletedInstances)); + } + + if (count($notDeletedInstances)) { + $this->messageManager->addErrorMessage(__( + 'Widget(s) with ID(s) %1 were not found', + implode(',', $notDeletedInstances) + )); + } + + /** @var Redirect $resultRedirect */ + $resultRedirect = $this->getResultPage(); + + return $resultRedirect->setPath('*/*/'); + } + + /** + * @return array + */ + private function getInstanceIds() + { + $instanceIds = $this->getRequest()->getParam('delete'); + + if (!is_array($instanceIds)) { + return []; + } + + return $instanceIds; + } + + /** + * @return ResultInterface|RedirectInterface + */ + private function getResultPage() + { + return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + } +} diff --git a/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php b/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php new file mode 100644 index 0000000000000..76feeb8f02f08 --- /dev/null +++ b/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php @@ -0,0 +1,72 @@ +resourceModel = $resourceModel; + $this->instanceFactory = $instanceFactory; + } + + /** + * Delete widget instance by given instance ID + * + * @param int $id + * @return void + */ + public function execute(int $id) + { + $model = $this->getById($id); + var_dump($model->getData(), $id); + $this->resourceModel->delete($model); + } + + /** + * @param int $id + * @return WidgetInstance + * @throws NoSuchEntityException + */ + private function getById(int $id) + { + $widgetInstance = $this->instanceFactory->create(); + + $this->resourceModel->load($widgetInstance, $id); + + if (!$widgetInstance->getId()) { + throw NoSuchEntityException::singleField('instance_id', $id); + } + + return $widgetInstance; + } +} diff --git a/app/code/Magento/Widget/etc/di.xml b/app/code/Magento/Widget/etc/di.xml index 2da5a351aa6ac..73199b0a34096 100644 --- a/app/code/Magento/Widget/etc/di.xml +++ b/app/code/Magento/Widget/etc/di.xml @@ -34,4 +34,6 @@ + From 7d92ade07f0173416efc9dc4e02a76c5d19c781f Mon Sep 17 00:00:00 2001 From: Vasilii Date: Wed, 30 Jan 2019 09:31:16 +0200 Subject: [PATCH 007/841] Added adjustments as per maintainer's code review --- .../Api/DeleteWidgetInstanceByIdInterface.php | 23 ------------------- .../Adminhtml/Widget/Instance/MassDelete.php | 13 ++++++----- .../Widget/Model/DeleteWidgetInstanceById.php | 21 +++++++++-------- 3 files changed, 18 insertions(+), 39 deletions(-) delete mode 100644 app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php diff --git a/app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php b/app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php deleted file mode 100644 index 3045912f7b0d2..0000000000000 --- a/app/code/Magento/Widget/Api/DeleteWidgetInstanceByIdInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -deleteWidgetInstanceById = $deleteWidgetInstanceById; @@ -49,6 +49,7 @@ public function __construct( * Execute action * * @return Redirect + * @throws \Exception */ public function execute() { @@ -66,7 +67,7 @@ public function execute() return $resultRedirect->setPath('*/*/'); } - foreach ($instanceIds as $key => $instanceId) { + foreach ($instanceIds as $instanceId) { try { $this->deleteWidgetInstanceById->execute((int) $instanceId); $deletedInstances++; @@ -82,7 +83,7 @@ public function execute() if (count($notDeletedInstances)) { $this->messageManager->addErrorMessage(__( 'Widget(s) with ID(s) %1 were not found', - implode(',', $notDeletedInstances) + trim(implode(', ', $notDeletedInstances)) )); } diff --git a/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php b/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php index 76feeb8f02f08..a830dbfcbd436 100644 --- a/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php +++ b/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php @@ -7,7 +7,6 @@ namespace Magento\Widget\Model; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Widget\Api\DeleteWidgetInstanceByIdInterface; use Magento\Widget\Model\ResourceModel\Widget\Instance as InstanceResourceModel; use Magento\Widget\Model\Widget\InstanceFactory as WidgetInstanceFactory; use Magento\Widget\Model\Widget\Instance as WidgetInstance; @@ -15,7 +14,7 @@ /** * Class DeleteWidgetInstanceById */ -class DeleteWidgetInstanceById implements DeleteWidgetInstanceByIdInterface +class DeleteWidgetInstanceById { /** * @var InstanceResourceModel @@ -42,29 +41,31 @@ public function __construct( /** * Delete widget instance by given instance ID * - * @param int $id + * @param int $instanceId * @return void + * @throws \Exception */ - public function execute(int $id) + public function execute(int $instanceId) { - $model = $this->getById($id); - var_dump($model->getData(), $id); + $model = $this->getWidgetById($instanceId); + $this->resourceModel->delete($model); } /** - * @param int $id + * @param int $instanceId * @return WidgetInstance * @throws NoSuchEntityException */ - private function getById(int $id) + private function getWidgetById(int $instanceId) { + /** @var WidgetInstance $widgetInstance */ $widgetInstance = $this->instanceFactory->create(); - $this->resourceModel->load($widgetInstance, $id); + $this->resourceModel->load($widgetInstance, $instanceId); if (!$widgetInstance->getId()) { - throw NoSuchEntityException::singleField('instance_id', $id); + throw NoSuchEntityException::singleField('instance_id', $instanceId); } return $widgetInstance; From 583ff118ba99d95454f0ef3e866f26928993ed91 Mon Sep 17 00:00:00 2001 From: Maksym Novik Date: Thu, 7 Feb 2019 14:53:08 +0200 Subject: [PATCH 008/841] Implement exception logging #220. Created custom error handler; Applied default error handling functionality --- app/etc/di.xml | 1 + .../Framework/GraphQl/Query/ErrorHandler.php | 29 +++++++++++++++++++ .../GraphQl/Query/ErrorHandlerInterface.php | 26 +++++++++++++++++ .../GraphQl/Query/QueryProcessor.php | 20 ++++++++++--- 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php create mode 100644 lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php diff --git a/app/etc/di.xml b/app/etc/di.xml index 6cf169c1d2277..dc884cb9fe29b 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -209,6 +209,7 @@ + diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php new file mode 100644 index 0000000000000..9fc3f9dc584e5 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php @@ -0,0 +1,29 @@ +exceptionFormatter = $exceptionFormatter; $this->queryComplexityLimiter = $queryComplexityLimiter; + $this->errorHandler = $errorHandler; } /** @@ -67,6 +77,8 @@ public function process( $contextValue, $variableValues, $operationName + )->setErrorsHandler( + [$this->errorHandler, 'handle'] )->toArray( $this->exceptionFormatter->shouldShowDetail() ? \GraphQL\Error\Debug::INCLUDE_DEBUG_MESSAGE | \GraphQL\Error\Debug::INCLUDE_TRACE : false From 9027c50f7fa39c6bca79c8023e2191d441a1d937 Mon Sep 17 00:00:00 2001 From: Maksym Novik Date: Thu, 7 Feb 2019 15:39:40 +0200 Subject: [PATCH 009/841] Implement exception logging #220. Implemented client and server errors logging --- app/etc/di.xml | 58 +++++++++++++- .../Framework/GraphQl/Query/ErrorHandler.php | 75 ++++++++++++++++++- .../GraphQl/Query/ErrorHandlerInterface.php | 3 + 3 files changed, 134 insertions(+), 2 deletions(-) diff --git a/app/etc/di.xml b/app/etc/di.xml index dc884cb9fe29b..ed9f32a1bf267 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -209,7 +209,6 @@ - @@ -1757,4 +1756,61 @@ + + + + GraphQLClientLogger + GraphQLServerLogger + GraphQLGeneralLogger + + \GraphQL\Error\Error::CATEGORY_GRAPHQL + \Magento\Framework\GraphQl\Exception\GraphQlAlreadyExistsException::EXCEPTION_CATEGORY + \Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException::EXCEPTION_CATEGORY + \Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException::EXCEPTION_CATEGORY + \Magento\Framework\GraphQl\Exception\GraphQlInputException::EXCEPTION_CATEGORY + \Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException::EXCEPTION_CATEGORY + request + user + + + \GraphQL\Error\Error::CATEGORY_INTERNAL + + + + + + + GraphQLClientErrorHandler + + + + + + \Magento\Framework\GraphQl\Query\ErrorHandlerInterface::CLIENT_LOG_FILE + + + + + + GraphQLServerErrorHandler + + + + + + \Magento\Framework\GraphQl\Query\ErrorHandlerInterface::SERVER_LOG_FILE + + + + + + GraphQLGeneralErrorHandler + + + + + + \Magento\Framework\GraphQl\Query\ErrorHandlerInterface::GENERAL_LOG_FILE + + diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php index 9fc3f9dc584e5..2f97ae238c6a7 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php @@ -14,6 +14,56 @@ */ class ErrorHandler implements ErrorHandlerInterface { + /** + * @var \Magento\Framework\Logger\Monolog + */ + private $clientLogger; + + /** + * @var \Magento\Framework\Logger\Monolog + */ + private $serverLogger; + + /** + * @var array + */ + private $clientErrorCategories; + + /** + * @var array + */ + private $serverErrorCategories; + + /** + * @var \Magento\Framework\Logger\Monolog + */ + private $generalLogger; + + /** + * ErrorHandler constructor. + * + * @param \Magento\Framework\Logger\Monolog $clientLogger + * @param \Magento\Framework\Logger\Monolog $serverLogger + * @param \Magento\Framework\Logger\Monolog $generalLogger + * @param array $clientErrorCategories + * @param array $serverErrorCategories + * + * @SuppressWarnings(PHPMD.LongVariable) + */ + public function __construct( + \Magento\Framework\Logger\Monolog $clientLogger, + \Magento\Framework\Logger\Monolog $serverLogger, + \Magento\Framework\Logger\Monolog $generalLogger, + array $clientErrorCategories = [], + array $serverErrorCategories = [] + ) { + $this->clientLogger = $clientLogger; + $this->serverLogger = $serverLogger; + $this->generalLogger = $generalLogger; + $this->clientErrorCategories = $clientErrorCategories; + $this->serverErrorCategories = $serverErrorCategories; + } + /** * Handle errors * @@ -24,6 +74,29 @@ class ErrorHandler implements ErrorHandlerInterface */ public function handle(array $errors, callable $formatter):array { - return array_map($formatter, $errors); + return array_map( + function (\GraphQL\Error\ClientAware $error) use ($formatter) { + $this->logError($error); + + return $formatter($error); + }, + $errors + ); + } + + /** + * @param \GraphQL\Error\ClientAware $error + * + * @return boolean + */ + private function logError(\GraphQL\Error\ClientAware $error):bool + { + if (in_array($error->getCategory(), $this->clientErrorCategories)) { + return $this->clientLogger->error($error); + } elseif (in_array($error->getCategory(), $this->serverErrorCategories)) { + return $this->serverLogger->error($error); + } + + return $this->generalLogger->error($error); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php index f1123864f4afc..f63468fa4ddb8 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php @@ -14,6 +14,9 @@ */ interface ErrorHandlerInterface { + const SERVER_LOG_FILE = 'var/log/graphql/server/exception.log'; + const CLIENT_LOG_FILE = 'var/log/graphql/client/exception.log'; + const GENERAL_LOG_FILE = 'var/log/graphql/exception.log'; /** * Handle errors * From a2551afc1cd3e8f43a6c9e05d9aa9f3939bcfda5 Mon Sep 17 00:00:00 2001 From: Vasilii Date: Sat, 16 Mar 2019 00:14:20 +0200 Subject: [PATCH 010/841] magento/magento2#5023 - It is not possible to add MS tile image meta via default_head_blocks.xml --- .../Magento/Framework/View/Page/Config.php | 29 +++++++++++++++++++ .../Framework/View/Page/Config/Renderer.php | 4 +++ .../Test/Unit/Page/Config/RendererTest.php | 5 ++++ .../View/Test/Unit/Page/ConfigTest.php | 14 +++++++++ 4 files changed, 52 insertions(+) diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index b29a0feda9d60..77b212988240f 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -130,6 +130,11 @@ class Config 'title' => null, ]; + /** + * @var array + */ + protected $additionalMetaAssets = ['msapplication-TileImage']; + /** * @var \Magento\Framework\App\State */ @@ -539,6 +544,30 @@ public function addBodyClass($className) return $this; } + /** + * Retrieve the additional meta assets + * + * @return array + */ + public function getAdditionalMetaAssets() + { + return $this->additionalMetaAssets; + } + /** + * Adjust metadata content url + * + * @param string $content + * @return string $content + */ + public function getMetaAssetUrl($content) + { + $parsed = parse_url($content); + if (empty($parsed['scheme'])) { + return $this->assetRepo->getUrl($content); + } + return $content; + } + /** * Set additional element attribute * diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php index ac46cf8a594cc..64c8016e9a31a 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php @@ -157,6 +157,10 @@ protected function processMetadataContent($name, $content) if (method_exists($this->pageConfig, $method)) { $content = $this->pageConfig->$method(); } + if ($content && in_array($name, $this->pageConfig->getAdditionalMetaAssets())) { + $content = $this->pageConfig->getMetaAssetUrl($content); + } + return $content; } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php index 1f110a9ec19b5..06e8834e7854b 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php @@ -171,6 +171,11 @@ public function testRenderMetadata() ->method('getMetadata') ->will($this->returnValue($metadata)); + $this->pageConfigMock + ->expects($this->any()) + ->method('getAdditionalMetaAssets') + ->will($this->returnValue(['msapplication-TileImage'])); + $this->assertEquals($expected, $this->renderer->renderMetadata()); } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php index 44e6e878a18c1..5422d8ec7a741 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php @@ -369,6 +369,20 @@ public function testAddRss() $this->assertInstanceOf(\Magento\Framework\View\Page\Config::class, $this->model->addRss($title, $href)); } + public function testGetMetaAssetUrlWithUrlInContent() + { + $this->assetRepo->expects($this->never())->method('getUrl'); + $this->assertEquals('http://test.com/image.png', $this->model->getMetaAssetUrl('http://test.com/image.png')); + } + + public function testGetMetaAssetUrlWithoutUrlInContent() + { + $this->assetRepo->expects($this->once())->method('getUrl')->with('image.png')->will( + $this->returnValue('http://test.com/image.png') + ); + $this->assertEquals('http://test.com/image.png', $this->model->getMetaAssetUrl('image.png')); + } + public function testAddBodyClass() { $className = 'test class'; From 3d193bfade9019a08f162e9bf9b30c625dc1b098 Mon Sep 17 00:00:00 2001 From: Vasilii Date: Sun, 17 Mar 2019 18:52:19 +0200 Subject: [PATCH 011/841] magento/magento2#5023 - It is not possible to add MS tile image meta via default_head_blocks.xml # Added codestyle fixes --- lib/internal/Magento/Framework/View/Page/Config.php | 1 + .../Magento/Framework/View/Test/Unit/Page/ConfigTest.php | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index 77b212988240f..72649a797730a 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -553,6 +553,7 @@ public function getAdditionalMetaAssets() { return $this->additionalMetaAssets; } + /** * Adjust metadata content url * diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php index 5422d8ec7a741..ce413e2a7892e 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php @@ -369,12 +369,18 @@ public function testAddRss() $this->assertInstanceOf(\Magento\Framework\View\Page\Config::class, $this->model->addRss($title, $href)); } + /** + * Testing case for getting meta asset URL with URL in content + */ public function testGetMetaAssetUrlWithUrlInContent() { $this->assetRepo->expects($this->never())->method('getUrl'); $this->assertEquals('http://test.com/image.png', $this->model->getMetaAssetUrl('http://test.com/image.png')); } + /** + * Testing case for getting meta asset URL without URL in content + */ public function testGetMetaAssetUrlWithoutUrlInContent() { $this->assetRepo->expects($this->once())->method('getUrl')->with('image.png')->will( From 2faa1ecc3ec8e69752070c31239385114d4941c4 Mon Sep 17 00:00:00 2001 From: Vasilii Date: Wed, 20 Mar 2019 09:40:14 +0200 Subject: [PATCH 012/841] magento/magento2#5023 - It is not possible to add MS tile image meta via default_head_blocks.xml # Changed the way how asset url is checked for URL scheme to a more consistent approach --- lib/internal/Magento/Framework/View/Page/Config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index 72649a797730a..b33bf1e154dda 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -562,10 +562,10 @@ public function getAdditionalMetaAssets() */ public function getMetaAssetUrl($content) { - $parsed = parse_url($content); - if (empty($parsed['scheme'])) { + if (!parse_url($content, PHP_URL_SCHEME)) { return $this->assetRepo->getUrl($content); } + return $content; } From 56a368fe74ca8f058a5f38b2b0b900d247e34724 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky Date: Fri, 22 Mar 2019 15:59:32 +0200 Subject: [PATCH 013/841] ENGCOM-4544: Semantic Version Checker fix. --- lib/internal/Magento/Framework/View/Page/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index b33bf1e154dda..0e48319bbe766 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -133,7 +133,7 @@ class Config /** * @var array */ - protected $additionalMetaAssets = ['msapplication-TileImage']; + private $additionalMetaAssets = ['msapplication-TileImage']; /** * @var \Magento\Framework\App\State From 8617785b5c9aa52d198bd6f91dca36adfa68cdf0 Mon Sep 17 00:00:00 2001 From: Vincent Marmiesse Date: Thu, 28 Mar 2019 11:08:10 +0100 Subject: [PATCH 014/841] Do not validate lenght and input rule if attribute value is required but empty --- app/code/Magento/Eav/Model/Attribute/Data/Text.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Text.php b/app/code/Magento/Eav/Model/Attribute/Data/Text.php index c5167821fdfce..a6fa24c1eff04 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/Text.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/Text.php @@ -75,6 +75,8 @@ public function validateValue($value) if (empty($value) && $value !== '0' && $attribute->getDefaultValue() === null) { $label = __($attribute->getStoreLabel()); $errors[] = __('"%1" is a required value.', $label); + + return $errors; } $validateLengthResult = $this->validateLength($attribute, $value); From d82ba2eb04a9eaa12a19dee75c8456f702515d82 Mon Sep 17 00:00:00 2001 From: Anahit Martirosyan Date: Fri, 29 Mar 2019 10:32:46 +0400 Subject: [PATCH 015/841] MAGETWO-51891: Category with invalid data loses new products assignment after validation - Add automated test script --- .../ActionGroup/AdminCategoryActionGroup.xml | 13 ++++ ...ssignmentToCategoryWithInvalidDataTest.xml | 62 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 90d732c9654e1..80e71ef89da90 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -316,4 +316,17 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml new file mode 100644 index 0000000000000..3ef8ecfeff304 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml @@ -0,0 +1,62 @@ + + + + + + + + + <description value="New products assignment to Category with invalid data after validation"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-98646"/> + <useCaseId value="MAGETWO-51891"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create two products and two categories--> + <comment userInput="Create two products and two categories" stepKey="commentCreateProductsAndCategories"/> + <createData entity="SimpleSubCategory" stepKey="categoryFirst"/> + <createData entity="SimpleSubCategory" stepKey="categorySecond"/> + <createData entity="SimpleProduct" stepKey="simpleProduct1"> + <requiredEntity createDataKey="categoryFirst"/> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <!--Delete created data--> + <comment userInput="Delete created data" stepKey="commentDeleteData"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="categoryFirst" stepKey="deleteCategory1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="categorySecond" stepKey="deleteCategory2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Assign product to category--> + <comment userInput="Assign product to category" stepKey="commentAssignProductToCategory"/> + <amOnPage url="{{AdminCategoryEditPage.url($$categoryFirst.id$$)}}" stepKey="goToProductCategoryPage"/> + <actionGroup ref="AddProductToCategory" stepKey="addProductToCategory"> + <argument name="productSku" value="$$simpleProduct2.sku$$"/> + </actionGroup> + <!-- Open Search Engine Optimization section and change URL key--> + <comment userInput="Open Search Engine Optimization section and change URL key" stepKey="commentChangeUrlKey"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="clickOnProductHeader"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="$$categorySecond.custom_attributes[url_key]$$" stepKey="changeUrlKey"/> + <!--Save URL key and see error message--> + <comment userInput="Save URL key and see error message" stepKey="commentSaveUrlKey"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveUrlKey"/> + <waitForPageLoad stepKey="waitForSaving"/> + <see userInput="The value specified in the URL Key field would generate a URL that already exists." stepKey="seeErrorMessage"/> + <!--Check product in category grid--> + <comment userInput="Check product in category grid" stepKey="commentCheckProduct"/> + <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductsInCategorySection"/> + <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct1.name$$)}}" stepKey="seeProductInCategoryGrid"/> + <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct2.name$$)}}" stepKey="seeProductInCategoryGrid2"/> + </test> +</tests> From 31427763b4a1e877adf18c098580dd3a9eb70791 Mon Sep 17 00:00:00 2001 From: kgbenka <kristian.goc-benka@gymbeam.com> Date: Wed, 3 Apr 2019 07:31:53 +0200 Subject: [PATCH 016/841] Wrong currency sing on creditmemo_grid & sales_order_view > invoice grid --- app/code/Magento/Sales/etc/db_schema.xml | 2 ++ app/code/Magento/Sales/etc/db_schema_whitelist.json | 4 +++- app/code/Magento/Sales/etc/di.xml | 2 ++ app/code/Magento/Sales/etc/module.xml | 2 +- .../adminhtml/ui_component/sales_order_creditmemo_grid.xml | 4 ++-- .../adminhtml/ui_component/sales_order_view_invoice_grid.xml | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Sales/etc/db_schema.xml b/app/code/Magento/Sales/etc/db_schema.xml index d6ea9b7d54861..4e91628f2fefc 100644 --- a/app/code/Magento/Sales/etc/db_schema.xml +++ b/app/code/Magento/Sales/etc/db_schema.xml @@ -1386,6 +1386,8 @@ comment="Adjustment Negative"/> <column xsi:type="decimal" name="order_base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Order Grand Total"/> + <column xsi:type="varchar" name="order_currency_code" nullable="true" length="3" comment="Order Currency Code"/> + <column xsi:type="varchar" name="base_currency_code" nullable="true" length="3" comment="Base Currency Code"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> </constraint> diff --git a/app/code/Magento/Sales/etc/db_schema_whitelist.json b/app/code/Magento/Sales/etc/db_schema_whitelist.json index 8790523c97c21..a7215d08c1f10 100644 --- a/app/code/Magento/Sales/etc/db_schema_whitelist.json +++ b/app/code/Magento/Sales/etc/db_schema_whitelist.json @@ -815,7 +815,9 @@ "shipping_and_handling": true, "adjustment_positive": true, "adjustment_negative": true, - "order_base_grand_total": true + "order_base_grand_total": true, + "order_currency_code": true, + "base_currency_code": true }, "index": { "SALES_CREDITMEMO_GRID_ORDER_INCREMENT_ID": true, diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index 68fcd17122bd2..a89b856bd5f10 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -640,6 +640,8 @@ <item name="adjustment_positive" xsi:type="string">sales_creditmemo.adjustment_positive</item> <item name="adjustment_negative" xsi:type="string">sales_creditmemo.adjustment_negative</item> <item name="order_base_grand_total" xsi:type="string">sales_order.base_grand_total</item> + <item name="order_currency_code" xsi:type="string">sales_order.order_currency_code</item> + <item name="base_currency_code" xsi:type="string">sales_order.base_currency_code</item> </argument> </arguments> </virtualType> diff --git a/app/code/Magento/Sales/etc/module.xml b/app/code/Magento/Sales/etc/module.xml index 11eebaa3d5a3d..e8af6b761a8a4 100644 --- a/app/code/Magento/Sales/etc/module.xml +++ b/app/code/Magento/Sales/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Sales" > + <module name="Magento_Sales"> <sequence> <module name="Magento_Rule"/> <module name="Magento_Catalog"/> diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml index f36a7d2821f7a..a365bfcf4b587 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_creditmemo_grid.xml @@ -119,7 +119,7 @@ <column name="base_grand_total" class="Magento\Sales\Ui\Component\Listing\Column\Price"> <settings> <filter>textRange</filter> - <label translate="true">Refunded</label> + <label translate="true">Refunded (Base)</label> </settings> </column> <column name="order_status" component="Magento_Ui/js/grid/columns/select"> @@ -194,7 +194,7 @@ <visible>false</visible> </settings> </column> - <column name="subtotal" class="Magento\Sales\Ui\Component\Listing\Column\Price"> + <column name="subtotal" class="Magento\Sales\Ui\Component\Listing\Column\PurchasedPrice"> <settings> <filter>textRange</filter> <label translate="true">Subtotal</label> diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml index ac1233c5e4961..c0ed1e01460bc 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_view_invoice_grid.xml @@ -130,7 +130,7 @@ <label translate="true">Status</label> </settings> </column> - <column name="grand_total" class="Magento\Sales\Ui\Component\Listing\Column\PurchasedPrice"> + <column name="base_grand_total" class="Magento\Sales\Ui\Component\Listing\Column\Price"> <settings> <filter>textRange</filter> <label translate="true">Amount</label> From 924b6b44f59087ff67be32f22a2c41468234488f Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Tue, 9 Apr 2019 15:44:58 +0400 Subject: [PATCH 017/841] MAGETWO-51891: Category with invalid data loses new products assignment after validation - Update automated test script --- .../NewProductsAssignmentToCategoryWithInvalidDataTest.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml index 3ef8ecfeff304..eb02db8553f6f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml @@ -40,7 +40,7 @@ </after> <!--Assign product to category--> <comment userInput="Assign product to category" stepKey="commentAssignProductToCategory"/> - <amOnPage url="{{AdminCategoryEditPage.url($$categoryFirst.id$$)}}" stepKey="goToProductCategoryPage"/> + <amOnPage url="{{AdminCategoryEditPage.url($$categoryFirst.id$$)}}" stepKey="goToCategoryPage"/> <actionGroup ref="AddProductToCategory" stepKey="addProductToCategory"> <argument name="productSku" value="$$simpleProduct2.sku$$"/> </actionGroup> @@ -56,7 +56,7 @@ <!--Check product in category grid--> <comment userInput="Check product in category grid" stepKey="commentCheckProduct"/> <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductsInCategorySection"/> - <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct1.name$$)}}" stepKey="seeProductInCategoryGrid"/> - <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct2.name$$)}}" stepKey="seeProductInCategoryGrid2"/> + <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct1.name$$)}}" stepKey="seeProduct1InCategoryGrid"/> + <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct2.name$$)}}" stepKey="seeProduct2InCategoryGrid"/> </test> </tests> From 60a465c9c83d4b2c33583f1028bf29f9143c516c Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Wed, 24 Apr 2019 15:43:18 +0530 Subject: [PATCH 018/841] Fixed Magento is no refreshing the cart page if you delete a product from cart side block --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index e66c66006246c..b6bb93fdd6f6b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -310,6 +310,10 @@ define([ if (response.success) { callback.call(this, elem, response); + var currentURL = window.location.pathname; + if (url.includes("/checkout/cart/")) { + location.reload(); + } } else { msg = response['error_message']; From c08dffb1d32d3c5786076bcf51c50fae622ede66 Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Wed, 24 Apr 2019 15:59:35 +0530 Subject: [PATCH 019/841] Fixed Magento is no refreshing the cart page if you delete a product from cart side block(update var name) --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index b6bb93fdd6f6b..9db8912cbffd0 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -311,7 +311,7 @@ define([ if (response.success) { callback.call(this, elem, response); var currentURL = window.location.pathname; - if (url.includes("/checkout/cart/")) { + if (currentURL.includes("/checkout/cart/")) { location.reload(); } } else { From 7d481bdfa764e6f8a20a9381c26f1b3696fe199f Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Thu, 25 Apr 2019 11:54:12 +0530 Subject: [PATCH 020/841] Fixed Magento is no refreshing the cart page if you delete a product from cart side block(reload only cart block) --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index 9db8912cbffd0..5929c2c21fa47 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -306,13 +306,13 @@ define([ } }) .done(function (response) { - var msg; + var msg, currentURL; if (response.success) { callback.call(this, elem, response); - var currentURL = window.location.pathname; + currentURL = window.location.pathname; if (currentURL.includes("/checkout/cart/")) { - location.reload(); + $("#shopping-cart-table").trigger('contentUpdated'); } } else { msg = response['error_message']; From ce6c2288f0a386bb79688e7e6d3232e519ec2ae3 Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Thu, 25 Apr 2019 12:20:09 +0530 Subject: [PATCH 021/841] Fixed Magento is no refreshing the cart page if you delete a product from cart side block(reload page on delete) --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index 5929c2c21fa47..be5f29911b4c1 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -312,7 +312,7 @@ define([ callback.call(this, elem, response); currentURL = window.location.pathname; if (currentURL.includes("/checkout/cart/")) { - $("#shopping-cart-table").trigger('contentUpdated'); + location.reload(); } } else { msg = response['error_message']; From 6e1dc006c6cee0bbb692a2d077414c0ffdab7d48 Mon Sep 17 00:00:00 2001 From: Anahit Martirosyan <anahit_martirosyan@epam.com> Date: Tue, 30 Apr 2019 15:51:27 +0400 Subject: [PATCH 022/841] MAGETWO-51891: Category with invalid data loses new products assignment after validation - Add updated test script --- .../Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 80e71ef89da90..57dff7fb2ae90 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -320,7 +320,7 @@ <arguments> <argument name="productSku" type="string"/> </arguments> - <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductsInCategorySection"/> + <conditionalClick selector="{{AdminCategoryProductsSection.sectionHeader}}" dependentSelector="{{AdminCategoryProductsSection.addProducts}}" visible="false" stepKey="openProductsInCategorySection"/> <click selector="{{AdminCategoryProductsSection.addProducts}}" stepKey="clickAddProductsToCategory"/> <waitForElementVisible selector="{{AdminCategoryAddProductsModalSection.filters}}" stepKey="waitForModalOpen" time="30"/> <click selector="{{AdminCategoryAddProductsModalSection.addProductBySku}}" stepKey="clickAddBySkuTab"/> From 6dfc8184af647e0f515a94e09ebee2f3b2cbf537 Mon Sep 17 00:00:00 2001 From: Evgeny Petrov <evgeny_petrov@epam.com> Date: Sat, 4 May 2019 15:22:27 +0300 Subject: [PATCH 023/841] MAGETWO-51891: Category with invalid data loses new products assignment after validation - Add updated test script --- ...ssignmentToCategoryWithInvalidDataTest.xml | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml deleted file mode 100644 index eb02db8553f6f..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Test/NewProductsAssignmentToCategoryWithInvalidDataTest.xml +++ /dev/null @@ -1,62 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="NewProductsAssignmentToCategoryWithInvalidDataTest"> - <annotations> - <features value="Catalog"/> - <title value="New products assignment to Category with invalid data after validation"/> - <description value="New products assignment to Category with invalid data after validation"/> - <severity value="MAJOR"/> - <testCaseId value="MAGETWO-98646"/> - <useCaseId value="MAGETWO-51891"/> - <group value="Catalog"/> - </annotations> - <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Create two products and two categories--> - <comment userInput="Create two products and two categories" stepKey="commentCreateProductsAndCategories"/> - <createData entity="SimpleSubCategory" stepKey="categoryFirst"/> - <createData entity="SimpleSubCategory" stepKey="categorySecond"/> - <createData entity="SimpleProduct" stepKey="simpleProduct1"> - <requiredEntity createDataKey="categoryFirst"/> - </createData> - <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> - </before> - <after> - <!--Delete created data--> - <comment userInput="Delete created data" stepKey="commentDeleteData"/> - <deleteData createDataKey="simpleProduct1" stepKey="deleteProduct1"/> - <deleteData createDataKey="categoryFirst" stepKey="deleteCategory1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteProduct2"/> - <deleteData createDataKey="categorySecond" stepKey="deleteCategory2"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> - <!--Assign product to category--> - <comment userInput="Assign product to category" stepKey="commentAssignProductToCategory"/> - <amOnPage url="{{AdminCategoryEditPage.url($$categoryFirst.id$$)}}" stepKey="goToCategoryPage"/> - <actionGroup ref="AddProductToCategory" stepKey="addProductToCategory"> - <argument name="productSku" value="$$simpleProduct2.sku$$"/> - </actionGroup> - <!-- Open Search Engine Optimization section and change URL key--> - <comment userInput="Open Search Engine Optimization section and change URL key" stepKey="commentChangeUrlKey"/> - <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="clickOnProductHeader"/> - <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="$$categorySecond.custom_attributes[url_key]$$" stepKey="changeUrlKey"/> - <!--Save URL key and see error message--> - <comment userInput="Save URL key and see error message" stepKey="commentSaveUrlKey"/> - <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveUrlKey"/> - <waitForPageLoad stepKey="waitForSaving"/> - <see userInput="The value specified in the URL Key field would generate a URL that already exists." stepKey="seeErrorMessage"/> - <!--Check product in category grid--> - <comment userInput="Check product in category grid" stepKey="commentCheckProduct"/> - <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductsInCategorySection"/> - <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct1.name$$)}}" stepKey="seeProduct1InCategoryGrid"/> - <seeElement selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$simpleProduct2.name$$)}}" stepKey="seeProduct2InCategoryGrid"/> - </test> -</tests> From 930ba4028399b15b1a46119d0d53e20157aafd36 Mon Sep 17 00:00:00 2001 From: Evgeny Petrov <evgeny_petrov@epam.com> Date: Sat, 11 May 2019 16:12:28 +0300 Subject: [PATCH 024/841] MAGETWO-51891: Category with invalid data loses new products assignment after validation --- .../Mftf/ActionGroup/AdminCategoryActionGroup.xml | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 57dff7fb2ae90..90d732c9654e1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -316,17 +316,4 @@ <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> <waitForPageLoad stepKey="waitForPageToLoad1"/> </actionGroup> - <actionGroup name="AddProductToCategory"> - <arguments> - <argument name="productSku" type="string"/> - </arguments> - <conditionalClick selector="{{AdminCategoryProductsSection.sectionHeader}}" dependentSelector="{{AdminCategoryProductsSection.addProducts}}" visible="false" stepKey="openProductsInCategorySection"/> - <click selector="{{AdminCategoryProductsSection.addProducts}}" stepKey="clickAddProductsToCategory"/> - <waitForElementVisible selector="{{AdminCategoryAddProductsModalSection.filters}}" stepKey="waitForModalOpen" time="30"/> - <click selector="{{AdminCategoryAddProductsModalSection.addProductBySku}}" stepKey="clickAddBySkuTab"/> - <fillField selector="{{AdminCategoryAddProductsModalSection.productSkuInput}}" userInput="{{productSku}}" stepKey="enterSku"/> - <click selector="{{AdminCategoryAddProductsModalSection.assign}}" stepKey="assignSku"/> - <waitForAjaxLoad stepKey="waitForAjax"/> - <click selector="{{AdminCategoryAddProductsModalSection.saveClose}}" stepKey="saveCloseModal"/> - </actionGroup> </actionGroups> From 08327f717eb4c7050072b6237f37e156bcd973eb Mon Sep 17 00:00:00 2001 From: Hailong Zhao <hailongzh@hotmail.com> Date: Thu, 9 May 2019 13:33:49 -0400 Subject: [PATCH 025/841] Allow sending email to BCC address when not notifying user. --- .../Magento/Sales/Model/Order/Email/Sender.php | 16 ++++++++++++---- .../Sales/Model/Order/Email/SenderBuilder.php | 4 ++-- .../Order/Creditmemo/Sender/EmailSenderTest.php | 4 ++++ .../Order/Email/Sender/CreditmemoSenderTest.php | 4 ++++ .../Order/Email/Sender/InvoiceSenderTest.php | 4 ++++ .../Model/Order/Email/Sender/OrderSenderTest.php | 8 ++++++++ .../Order/Email/Sender/ShipmentSenderTest.php | 4 ++++ .../Unit/Model/Order/Email/SenderBuilderTest.php | 3 --- .../Order/Invoice/Sender/EmailSenderTest.php | 4 ++++ .../Order/Shipment/Sender/EmailSenderTest.php | 4 ++++ 10 files changed, 46 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender.php b/app/code/Magento/Sales/Model/Order/Email/Sender.php index 564fd1e2a4b98..b437c3f5fbf72 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender.php @@ -18,6 +18,12 @@ */ abstract class Sender { + /** + * Copy methods + */ + const COPY_METHOD_BCC = 'bcc'; + const COPY_METHOD_COPY = 'copy'; + /** * @var \Magento\Sales\Model\Order\Email\SenderBuilderFactory */ @@ -87,10 +93,12 @@ protected function checkAndSend(Order $order) $this->logger->error($e->getMessage()); return false; } - try { - $sender->sendCopyTo(); - } catch (\Exception $e) { - $this->logger->error($e->getMessage()); + if ($this->identityContainer->getCopyMethod() == self::COPY_METHOD_COPY) { + try { + $sender->sendCopyTo(); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + } } return true; } diff --git a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php index ed9e38822245f..788baa05cadb0 100644 --- a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php +++ b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php @@ -65,7 +65,7 @@ public function send() $copyTo = $this->identityContainer->getEmailCopyTo(); - if (!empty($copyTo) && $this->identityContainer->getCopyMethod() == 'bcc') { + if (!empty($copyTo) && $this->identityContainer->getCopyMethod() == Sender::COPY_METHOD_BCC) { foreach ($copyTo as $email) { $this->transportBuilder->addBcc($email); } @@ -84,7 +84,7 @@ public function sendCopyTo() { $copyTo = $this->identityContainer->getEmailCopyTo(); - if (!empty($copyTo) && $this->identityContainer->getCopyMethod() == 'copy') { + if (!empty($copyTo)) { foreach ($copyTo as $email) { $this->configureEmailTemplate(); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php index 467476c9bb406..09cf7f387449f 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php @@ -283,6 +283,10 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->willReturn($emailSendingResult); if ($emailSendingResult) { + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->subject::COPY_METHOD_COPY); + $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->senderMock); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php index 1f074d7262f4d..442f7892cdbd3 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php @@ -138,6 +138,10 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema ->willReturn($emailSendingResult); if ($emailSendingResult) { + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->sender::COPY_METHOD_COPY); + $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->senderMock); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php index d1aa5af53da4d..20f63aeac147a 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php @@ -144,6 +144,10 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema ->willReturn($emailSendingResult); if ($emailSendingResult) { + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->sender::COPY_METHOD_COPY); + $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->senderMock); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php index 88053ea684ce8..c6399731f2745 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php @@ -77,6 +77,10 @@ public function testSend($configValue, $forceSyncMode, $emailSendingResult, $sen ->willReturn($emailSendingResult); if ($emailSendingResult) { + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->sender::COPY_METHOD_COPY); + $addressMock = $this->createMock(\Magento\Sales\Model\Order\Address::class); $this->addressRenderer->expects($this->any()) @@ -214,6 +218,10 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte ->method('isEnabled') ->willReturn(true); + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->sender::COPY_METHOD_COPY); + $addressMock = $this->createMock(\Magento\Sales\Model\Order\Address::class); $this->addressRenderer->expects($this->exactly($formatCallCount)) diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php index 2d7b42bccae5a..b86b02914e17d 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php @@ -144,6 +144,10 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema ->willReturn($emailSendingResult); if ($emailSendingResult) { + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->sender::COPY_METHOD_COPY); + $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->senderMock); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php index 24cd54e3a46b3..650955eca5c70 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php @@ -165,9 +165,6 @@ public function testSendCopyTo() $transportMock = $this->createMock( \Magento\Sales\Test\Unit\Model\Order\Email\Stub\TransportInterfaceMock::class ); - $this->identityContainerMock->expects($this->once()) - ->method('getCopyMethod') - ->will($this->returnValue('copy')); $this->identityContainerMock->expects($this->never()) ->method('getCustomerEmail'); $this->identityContainerMock->expects($this->never()) diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php index dcf689cf7d53b..fd090197a01af 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php @@ -282,6 +282,10 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->willReturn($emailSendingResult); if ($emailSendingResult) { + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->subject::COPY_METHOD_COPY); + $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->senderMock); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php index 391e99ba6f835..91beca535ff27 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php @@ -284,6 +284,10 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending ->willReturn($emailSendingResult); if ($emailSendingResult) { + $this->identityContainerMock->expects($this->once()) + ->method('getCopyMethod') + ->willReturn($this->subject::COPY_METHOD_COPY); + $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->senderMock); From e79e57be45d798763728e4449443616df07cda5a Mon Sep 17 00:00:00 2001 From: Maksym Novik <m.novik@ism-ukraine.com> Date: Thu, 16 May 2019 11:08:18 +0300 Subject: [PATCH 026/841] Magento2 Attribute option does not validate for existing records before insert #16852 --- .../Eav/Setup/AddOptionToAttribute.php | 210 +++++++++++++++++ app/code/Magento/Eav/Setup/EavSetup.php | 69 +----- .../Unit/Setup/AddAttributeOptionTest.php | 200 ++++++++++++++++ .../Eav/Setup/AddOptionToAttributeTest.php | 222 ++++++++++++++++++ .../Eav/_files/attribute_with_options.php | 46 ++++ .../attribute_with_options_rollback.php | 20 ++ 6 files changed, 710 insertions(+), 57 deletions(-) create mode 100644 app/code/Magento/Eav/Setup/AddOptionToAttribute.php create mode 100644 app/code/Magento/Eav/Test/Unit/Setup/AddAttributeOptionTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Eav/Setup/AddOptionToAttributeTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options.php create mode 100644 dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options_rollback.php diff --git a/app/code/Magento/Eav/Setup/AddOptionToAttribute.php b/app/code/Magento/Eav/Setup/AddOptionToAttribute.php new file mode 100644 index 0000000000000..c6b13f8a6e3ec --- /dev/null +++ b/app/code/Magento/Eav/Setup/AddOptionToAttribute.php @@ -0,0 +1,210 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Setup; + +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Setup\ModuleDataSetupInterface; + +/** + * Add option to attribute + */ +class AddOptionToAttribute +{ + /** + * @var ModuleDataSetupInterface + */ + private $setup; + + /** + * @param ModuleDataSetupInterface $setup + */ + public function __construct( + ModuleDataSetupInterface $setup + ) { + $this->setup = $setup; + } + + /** + * Add Attribute Option + * + * @param array $option + * + * @return void + * @throws LocalizedException + */ + public function execute(array $option): void + { + $optionTable = $this->setup->getTable('eav_attribute_option'); + $optionValueTable = $this->setup->getTable('eav_attribute_option_value'); + + if (isset($option['value'])) { + $this->addValue($option, $optionTable, $optionValueTable); + } elseif (isset($option['values'])) { + $this->addValues($option, $optionTable, $optionValueTable); + } + } + + /** + * Add option value + * + * @param array $option + * @param string $optionTable + * @param string $optionValueTable + * + * @return void + * @throws LocalizedException + */ + private function addValue(array $option, string $optionTable, string $optionValueTable): void + { + $value = $option['value']; + foreach ($value as $optionId => $values) { + $intOptionId = (int)$optionId; + if (!empty($option['delete'][$optionId])) { + if ($intOptionId) { + $condition = ['option_id =?' => $intOptionId]; + $this->setup->getConnection()->delete($optionTable, $condition); + } + continue; + } + + if (!$intOptionId) { + $data = [ + 'attribute_id' => $option['attribute_id'], + 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, + ]; + $this->setup->getConnection()->insert($optionTable, $data); + $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable); + } else { + $data = [ + 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, + ]; + $this->setup->getConnection()->update( + $optionTable, + $data, + ['option_id=?' => $intOptionId] + ); + } + + // Default value + if (!isset($values[0])) { + throw new LocalizedException( + __("The default option isn't defined. Set the option and try again.") + ); + } + $condition = ['option_id =?' => $intOptionId]; + $this->setup->getConnection()->delete($optionValueTable, $condition); + foreach ($values as $storeId => $value) { + $data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value]; + $this->setup->getConnection()->insert($optionValueTable, $data); + } + } + } + + /** + * Add option values + * + * @param array $option + * @param string $optionTable + * @param string $optionValueTable + * + * @return void + */ + private function addValues(array $option, string $optionTable, string $optionValueTable): void + { + $values = $option['values']; + $attributeId = (int)$option['attribute_id']; + $existingOptions = $this->getExistingAttributeOptions($attributeId, $optionTable, $optionValueTable); + foreach ($values as $sortOrder => $value) { + // add option + $data = ['attribute_id' => $attributeId, 'sort_order' => $sortOrder]; + if (!$this->isExistingOptionValue($value, $existingOptions)) { + $this->setup->getConnection()->insert($optionTable, $data); + + //add option value + $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable); + $data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $value]; + $this->setup->getConnection()->insert($optionValueTable, $data); + } elseif ($optionId = $this->getExistingOptionIdWithDiffSortOrder( + $sortOrder, + $value, + $existingOptions + ) + ) { + $this->setup->getConnection()->update( + $optionTable, + ['sort_order' => $sortOrder], + ['option_id = ?' => $optionId] + ); + } + } + } + + /** + * Check if option value already exists + * + * @param string $value + * @param array $existingOptions + * + * @return bool + */ + private function isExistingOptionValue(string $value, array $existingOptions): bool + { + foreach ($existingOptions as $option) { + if ($option['value'] == $value) { + return true; + } + } + + return false; + } + + /** + * Get existing attribute options + * + * @param int $attributeId + * @param string $optionTable + * @param string $optionValueTable + * + * @return array + */ + private function getExistingAttributeOptions(int $attributeId, string $optionTable, string $optionValueTable): array + { + $select = $this->setup + ->getConnection() + ->select() + ->from(['o' => $optionTable]) + ->reset('columns') + ->columns(['option_id', 'sort_order']) + ->join(['ov' => $optionValueTable], 'o.option_id = ov.option_id', 'value') + ->where(AttributeInterface::ATTRIBUTE_ID . ' = ?', $attributeId) + ->where('store_id = 0'); + + return $this->setup->getConnection()->fetchAll($select); + } + + /** + * Check if option already exists, but sort_order differs + * + * @param int $sortOrder + * @param string $value + * @param array $existingOptions + * + * @return int|null + */ + private function getExistingOptionIdWithDiffSortOrder(int $sortOrder, string $value, array $existingOptions): ?int + { + foreach ($existingOptions as $option) { + if ($option['value'] == $value && $option['sort_order'] != $sortOrder) { + return (int)$option['option_id']; + } + } + + return null; + } +} diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php index de285e81b1d03..c0eddf1d14f88 100644 --- a/app/code/Magento/Eav/Setup/EavSetup.php +++ b/app/code/Magento/Eav/Setup/EavSetup.php @@ -82,6 +82,11 @@ class EavSetup */ private $_defaultAttributeSetName = 'Default'; + /** + * @var AddOptionToAttribute + */ + private $addAttributeOption; + /** * @var Code */ @@ -95,18 +100,23 @@ class EavSetup * @param CacheInterface $cache * @param CollectionFactory $attrGroupCollectionFactory * @param Code|null $attributeCodeValidator + * @param AddOptionToAttribute|null $addAttributeOption + * @SuppressWarnings(PHPMD.LongVariable) */ public function __construct( ModuleDataSetupInterface $setup, Context $context, CacheInterface $cache, CollectionFactory $attrGroupCollectionFactory, - Code $attributeCodeValidator = null + Code $attributeCodeValidator = null, + AddOptionToAttribute $addAttributeOption = null ) { $this->cache = $cache; $this->attrGroupCollectionFactory = $attrGroupCollectionFactory; $this->attributeMapper = $context->getAttributeMapper(); $this->setup = $setup; + $this->addAttributeOption = $addAttributeOption + ?? ObjectManager::getInstance()->get(AddOptionToAttribute::class); $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get( Code::class ); @@ -868,62 +878,7 @@ public function addAttribute($entityTypeId, $code, array $attr) */ public function addAttributeOption($option) { - $optionTable = $this->setup->getTable('eav_attribute_option'); - $optionValueTable = $this->setup->getTable('eav_attribute_option_value'); - - if (isset($option['value'])) { - foreach ($option['value'] as $optionId => $values) { - $intOptionId = (int)$optionId; - if (!empty($option['delete'][$optionId])) { - if ($intOptionId) { - $condition = ['option_id =?' => $intOptionId]; - $this->setup->getConnection()->delete($optionTable, $condition); - } - continue; - } - - if (!$intOptionId) { - $data = [ - 'attribute_id' => $option['attribute_id'], - 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, - ]; - $this->setup->getConnection()->insert($optionTable, $data); - $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable); - } else { - $data = [ - 'sort_order' => isset($option['order'][$optionId]) ? $option['order'][$optionId] : 0, - ]; - $this->setup->getConnection()->update( - $optionTable, - $data, - ['option_id=?' => $intOptionId] - ); - } - - // Default value - if (!isset($values[0])) { - throw new \Magento\Framework\Exception\LocalizedException( - __("The default option isn't defined. Set the option and try again.") - ); - } - $condition = ['option_id =?' => $intOptionId]; - $this->setup->getConnection()->delete($optionValueTable, $condition); - foreach ($values as $storeId => $value) { - $data = ['option_id' => $intOptionId, 'store_id' => $storeId, 'value' => $value]; - $this->setup->getConnection()->insert($optionValueTable, $data); - } - } - } elseif (isset($option['values'])) { - foreach ($option['values'] as $sortOrder => $label) { - // add option - $data = ['attribute_id' => $option['attribute_id'], 'sort_order' => $sortOrder]; - $this->setup->getConnection()->insert($optionTable, $data); - $intOptionId = $this->setup->getConnection()->lastInsertId($optionTable); - - $data = ['option_id' => $intOptionId, 'store_id' => 0, 'value' => $label]; - $this->setup->getConnection()->insert($optionValueTable, $data); - } - } + $this->addAttributeOption->execute($option); } /** diff --git a/app/code/Magento/Eav/Test/Unit/Setup/AddAttributeOptionTest.php b/app/code/Magento/Eav/Test/Unit/Setup/AddAttributeOptionTest.php new file mode 100644 index 0000000000000..17376ceebbcb4 --- /dev/null +++ b/app/code/Magento/Eav/Test/Unit/Setup/AddAttributeOptionTest.php @@ -0,0 +1,200 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Test\Unit\Setup; + +use Magento\Eav\Setup\AddOptionToAttribute; +use Magento\Framework\DB\Adapter\Pdo\Mysql; +use Magento\Framework\DB\Select; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * Coverage for \Magento\Eav\Setup\AddOptionToAttribute + */ +class AddAttributeOptionTest extends TestCase +{ + /** + * @var AddOptionToAttribute + */ + private $operation; + + /** + * @var MockObject + */ + private $connectionMock; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + $setupMock = $this->createMock(ModuleDataSetupInterface::class); + $this->connectionMock = $this->createMock(Mysql::class); + $this->connectionMock->method('select') + ->willReturn($objectManager->getObject(Select::class)); + + $setupMock->method('getTable')->willReturn('some_table'); + $setupMock->method('getConnection')->willReturn($this->connectionMock); + + $this->operation = new AddOptionToAttribute($setupMock); + } + + /** + * @throws LocalizedException + */ + public function testAddNewOptions() + { + $this->connectionMock->method('fetchAll')->willReturn([]); + $this->connectionMock->expects($this->exactly(4))->method('insert'); + + $this->operation->execute( + [ + 'values' => ['Black', 'White'], + 'attribute_id' => 4 + ] + ); + } + + /** + * @throws LocalizedException + */ + public function testAddExistingOptionsWithTheSameSortOrder() + { + $this->connectionMock->method('fetchAll')->willReturn( + [ + ['option_id' => 1, 'sort_order' => 0, 'value' => 'Black'], + ['option_id' => 2, 'sort_order' => 1, 'value' => 'White'], + ] + ); + + $this->connectionMock->expects($this->never())->method('insert'); + $this->connectionMock->expects($this->never())->method('update'); + + $this->operation->execute( + [ + 'values' => ['Black', 'White'], + 'attribute_id' => 4 + ] + ); + } + + /** + * @throws LocalizedException + */ + public function testAddExistingOptionsWithDifferentSortOrder() + { + $this->connectionMock->method('fetchAll')->willReturn( + [ + ['option_id' => 1, 'sort_order' => 13, 'value' => 'Black'], + ['option_id' => 2, 'sort_order' => 666, 'value' => 'White'], + ] + ); + + $this->connectionMock->expects($this->never())->method('insert'); + $this->connectionMock->expects($this->exactly(2))->method('update'); + + $this->operation->execute( + [ + 'values' => ['Black', 'White'], + 'attribute_id' => 4 + ] + ); + } + + /** + * @throws LocalizedException + */ + public function testAddMixedOptions() + { + $this->connectionMock->method('fetchAll')->willReturn( + [ + ['option_id' => 1, 'sort_order' => 13, 'value' => 'Black'], + ] + ); + + $this->connectionMock->expects($this->exactly(2))->method('insert'); + $this->connectionMock->expects($this->once())->method('update'); + + $this->operation->execute( + [ + 'values' => ['Black', 'White'], + 'attribute_id' => 4 + ] + ); + } + + /** + * @throws LocalizedException + */ + public function testAddNewOption() + { + $this->connectionMock->expects($this->exactly(2))->method('insert'); + $this->connectionMock->expects($this->once())->method('delete'); + + $this->operation->execute( + [ + 'attribute_id' => 1, + 'order' => [0 => 13], + 'value' => [ + [ + 0 => 'zzz', + ], + ], + ] + ); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage The default option isn't defined. Set the option and try again. + */ + public function testAddNewOptionWithoutDefaultValue() + { + $this->operation->execute( + [ + 'attribute_id' => 1, + 'order' => [0 => 13], + 'value' => [[]], + ] + ); + } + + public function testDeleteOption() + { + $this->connectionMock->expects($this->never())->method('insert'); + $this->connectionMock->expects($this->never())->method('update'); + $this->connectionMock->expects($this->once())->method('delete'); + + $this->operation->execute( + [ + 'attribute_id' => 1, + 'delete' => [13 => true], + 'value' => [ + 13 => null, + ], + ] + ); + } + + public function testUpdateOption() + { + $this->connectionMock->expects($this->once())->method('insert'); + $this->connectionMock->expects($this->once())->method('update'); + $this->connectionMock->expects($this->once())->method('delete'); + + $this->operation->execute( + [ + 'attribute_id' => 1, + 'value' => [ + 13 => ['zzz'], + ], + ] + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/Setup/AddOptionToAttributeTest.php b/dev/tests/integration/testsuite/Magento/Eav/Setup/AddOptionToAttributeTest.php new file mode 100644 index 0000000000000..c79a4e6caddba --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/Setup/AddOptionToAttributeTest.php @@ -0,0 +1,222 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Eav\Setup; + +use Magento\Catalog\Model\Product; +use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\Eav\Api\Data\AttributeInterface; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use PHPUnit\Framework\TestCase; + +/** + * Tests coverage for \Magento\Eav\Setup\AddOptionToAttribute + */ +class AddOptionToAttributeTest extends TestCase +{ + /** + * @var AddOptionToAttribute + */ + private $operation; + + /** + * @var int + */ + private $attributeId; + + /** + * @var EavSetup + */ + private $eavSetup; + + /** + * @var AttributeRepositoryInterface + */ + private $attrRepo; + + /** + * @var ModuleDataSetupInterface + */ + private $setup; + + protected function setUp() + { + $objectManager = ObjectManager::getInstance(); + + $this->operation = $objectManager->get(AddOptionToAttribute::class); + /** @var ModuleDataSetupInterface $setup */ + $this->setup = $objectManager->get(ModuleDataSetupInterface::class); + /** @var AttributeRepositoryInterface attrRepo */ + $this->attrRepo = $objectManager->get(AttributeRepositoryInterface::class); + /** @var EavSetup $eavSetup */ + $this->eavSetup = $objectManager->get(EavSetupFactory::class) + ->create(['setup' => $this->setup]); + $this->attributeId = $this->eavSetup->getAttributeId(Product::ENTITY, 'zzz'); + } + + /** + * @param bool $fetchPairs + * + * @return array + */ + private function getAttributeOptions($fetchPairs = true): array + { + $optionTable = $this->setup->getTable('eav_attribute_option'); + $optionValueTable = $this->setup->getTable('eav_attribute_option_value'); + + $select = $this->setup + ->getConnection() + ->select() + ->from(['o' => $optionTable]) + ->reset('columns') + ->columns('sort_order') + ->join(['ov' => $optionValueTable], 'o.option_id = ov.option_id', 'value') + ->where(AttributeInterface::ATTRIBUTE_ID . ' = ?', $this->attributeId) + ->where('store_id = 0'); + + return $fetchPairs + ? $this->setup->getConnection()->fetchPairs($select) + : $this->setup->getConnection()->fetchAll($select); + } + + /** + * @magentoDataFixture Magento/Eav/_files/attribute_with_options.php + */ + public function testAddNewOptions() + { + $optionsBefore = $this->getAttributeOptions(false); + $this->operation->execute( + [ + 'values' => ['new1', 'new2'], + 'attribute_id' => $this->attributeId + ] + ); + $optionsAfter = $this->getAttributeOptions(false); + $this->assertEquals(count($optionsBefore) + 2, count($optionsAfter)); + $this->assertArraySubset($optionsBefore, $optionsAfter); + } + + /** + * @magentoDataFixture Magento/Eav/_files/attribute_with_options.php + */ + public function testAddExistingOptionsWithTheSameSortOrder() + { + $optionsBefore = $this->getAttributeOptions(); + $this->operation->execute( + [ + 'values' => ['Black', 'White'], + 'attribute_id' => $this->attributeId + ] + ); + $optionsAfter = $this->getAttributeOptions(); + $this->assertEquals(count($optionsBefore), count($optionsAfter)); + $this->assertArraySubset($optionsBefore, $optionsAfter); + } + + /** + * @magentoDataFixture Magento/Eav/_files/attribute_with_options.php + */ + public function testAddExistingOptionsWithDifferentSortOrder() + { + $optionsBefore = $this->getAttributeOptions(); + $this->operation->execute( + [ + 'values' => [666 => 'White', 777 => 'Black'], + 'attribute_id' => $this->attributeId + ] + ); + $optionsAfter = $this->getAttributeOptions(); + $this->assertSameSize($optionsBefore, array_intersect($optionsBefore, $optionsAfter)); + $this->assertEquals($optionsAfter[777], $optionsBefore[0]); + $this->assertEquals($optionsAfter[666], $optionsBefore[1]); + } + + /** + * @magentoDataFixture Magento/Eav/_files/attribute_with_options.php + */ + public function testAddMixedOptions() + { + $sizeBefore = count($this->getAttributeOptions()); + $this->operation->execute( + [ + 'values' => [666 => 'Black', 'NewOption'], + 'attribute_id' => $this->attributeId + ] + ); + $updatedOptions = $this->getAttributeOptions(); + $this->assertEquals(count($updatedOptions), $sizeBefore + 1); + $this->assertEquals($updatedOptions[666], 'Black'); + $this->assertEquals($updatedOptions[667], 'NewOption'); + } + + /** + * @magentoDataFixture Magento/Eav/_files/attribute_with_options.php + */ + public function testAddNewOption() + { + $sizeBefore = count($this->getAttributeOptions()); + + $this->operation->execute( + [ + 'attribute_id' => $this->attributeId, + 'order' => [0 => 13], + 'value' => [ + [ + 0 => 'NewOption', + ], + ], + ] + ); + $updatedOptions = $this->getAttributeOptions(); + $this->assertEquals(count($updatedOptions), $sizeBefore + 1); + $this->assertEquals($updatedOptions[13], 'NewOption'); + } + + /** + * @magentoDataFixture Magento/Eav/_files/attribute_with_options.php + */ + public function testDeleteOption() + { + $optionsBefore = $this->getAttributeOptions(); + $options = $this->attrRepo->get(Product::ENTITY, $this->attributeId)->getOptions(); + /** @var AttributeOptionInterface $optionToDelete */ + $optionToDelete = end($options); + $this->operation->execute( + [ + 'attribute_id' => $this->attributeId, + 'delete' => [$optionToDelete->getValue() => true], + 'value' => [ + $optionToDelete->getValue() => null, + ], + ] + ); + $updatedOptions = $this->getAttributeOptions(); + $this->assertArraySubset($updatedOptions, $optionsBefore); + $this->assertEquals(count($updatedOptions), count($optionsBefore) - 1); + } + + /** + * @magentoDataFixture Magento/Eav/_files/attribute_with_options.php + */ + public function testUpdateOption() + { + $optionsBefore = $this->getAttributeOptions(); + $this->operation->execute( + [ + 'attribute_id' => $this->attributeId, + 'value' => [ + 0 => ['updatedValue'], + ], + ] + ); + $optionsAfter = $this->getAttributeOptions(); + $this->assertEquals($optionsAfter[0], 'updatedValue'); + $this->assertSame(array_slice($optionsBefore, 1), array_slice($optionsAfter, 1)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options.php b/dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options.php new file mode 100644 index 0000000000000..a53bdc68e6b1a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Eav\Setup\EavSetup; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +$setup = $objectManager->get(ModuleDataSetupInterface::class); +/** @var EavSetup $eavSetup */ +$eavSetup = $objectManager->get(EavSetupFactory::class) + ->create(['setup' => $setup]); +$eavSetup->addAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'zzz', + [ + 'type' => 'int', + 'backend' => '', + 'frontend' => '', + 'label' => 'zzz', + 'input' => 'select', + 'class' => '', + 'source' => '', + 'global' => 1, + 'visible' => true, + 'required' => true, + 'user_defined' => true, + 'default' => null, + 'searchable' => false, + 'filterable' => false, + 'comparable' => false, + 'visible_on_front' => false, + 'used_in_product_listing' => false, + 'unique' => true, + 'apply_to' => '', + 'system' => 1, + 'group' => 'General', + 'option' => ['values' => ["Black", "White", "Red", "Brown", "zzz", "Metallic"]] + ] +); diff --git a/dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options_rollback.php b/dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options_rollback.php new file mode 100644 index 0000000000000..5b26403c797ec --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Eav/_files/attribute_with_options_rollback.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Catalog\Model\Product; +use Magento\Eav\Setup\EavSetup; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +$setup = $objectManager->get(ModuleDataSetupInterface::class); +/** @var EavSetup $eavSetup */ +$eavSetup = $objectManager->get(EavSetupFactory::class) + ->create(['setup' => $setup]); +$eavSetup->removeAttribute(Product::ENTITY, 'zzz'); From a8ec393227a5aeca1ba9aca5c33064cd6e3d9a61 Mon Sep 17 00:00:00 2001 From: Nazarn96 <nazarn96@gmail.com> Date: Wed, 22 May 2019 15:37:33 +0300 Subject: [PATCH 027/841] magento/magento#21424 static-test-fix --- app/code/Magento/Eav/Setup/EavSetup.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php index c0eddf1d14f88..d440a84fc8e65 100644 --- a/app/code/Magento/Eav/Setup/EavSetup.php +++ b/app/code/Magento/Eav/Setup/EavSetup.php @@ -577,6 +577,7 @@ public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder = nul if (empty($data['attribute_group_code'])) { if (empty($attributeGroupCode)) { // in the following code md5 is not used for security purposes + // phpcs:disable Magento2.Security.InsecureFunction $attributeGroupCode = md5($name); } $data['attribute_group_code'] = $attributeGroupCode; From 7f88d9119677e8f7077c1338950550252fa823d7 Mon Sep 17 00:00:00 2001 From: Navarr Barnier <navarr@mediotype.com> Date: Thu, 23 May 2019 13:22:22 -0400 Subject: [PATCH 028/841] Add a MessageFormatter Phrase renderer This gives us the ability to use ICU MessageFormatter formatting strings (inc. placeholders) for better support of internationalization. --- app/code/Magento/Translation/etc/di.xml | 1 + .../Phrase/Renderer/MessageFormatter.php | 44 +++++++ .../Unit/Renderer/MessageFormatterTest.php | 109 ++++++++++++++++++ lib/internal/Magento/Framework/composer.json | 1 + 4 files changed, 155 insertions(+) create mode 100644 lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php create mode 100644 lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php diff --git a/app/code/Magento/Translation/etc/di.xml b/app/code/Magento/Translation/etc/di.xml index 6d3ca03953cf9..88f6d54828733 100644 --- a/app/code/Magento/Translation/etc/di.xml +++ b/app/code/Magento/Translation/etc/di.xml @@ -43,6 +43,7 @@ <arguments> <argument name="renderers" xsi:type="array"> <item name="translation" xsi:type="object">Magento\Framework\Phrase\Renderer\Translate</item> + <item name="messageFormatter" xsi:type="object">Magento\Framework\Phrase\Renderer\MessageFormatter</item> <item name="placeholder" xsi:type="object">Magento\Framework\Phrase\Renderer\Placeholder</item> <item name="inline" xsi:type="object">Magento\Framework\Phrase\Renderer\Inline</item> </argument> diff --git a/lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php b/lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php new file mode 100644 index 0000000000000..575b37c264e87 --- /dev/null +++ b/lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Phrase\Renderer; + +use Magento\Framework\Phrase\RendererInterface; +use Magento\Framework\TranslateInterface; + +/** + * Process texts to resolve ICU MessageFormat + */ +class MessageFormatter implements RendererInterface +{ + /** @var TranslateInterface */ + private $translate; + + /** + * @param TranslateInterface $translate + */ + public function __construct(TranslateInterface $translate) + { + $this->translate = $translate; + } + + /** + * @inheritDoc + */ + public function render(array $source, array $arguments) + { + $text = end($source); + + if (strpos($text, '{') === false) { + // About 5x faster for non-MessageFormatted strings + // Only slightly slower for MessageFormatted strings (~0.3x) + return $text; + } + + $result = \MessageFormatter::formatMessage($this->translate->getLocale(), $text, $arguments); + return $result !== false ? $result : $text; + } +} diff --git a/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php b/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php new file mode 100644 index 0000000000000..f01dc6ef01b3f --- /dev/null +++ b/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php @@ -0,0 +1,109 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Phrase\Test\Unit\Renderer; + +use Magento\Framework\Phrase\Renderer\MessageFormatter; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Translate; + +/** + * Tests that messages sent through the MessageFormatter phrase renderer result in what would be expected when sent + * through PHP's native MessageFormatter, and that the locale is pulled from the Translate dependency + */ +class MessageFormatterTest extends \PHPUnit\Framework\TestCase +{ + /** @var ObjectManager */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + } + + /** + * Retrieve test cases + * + * @return array [Raw Phrase, Locale, Arguments, Expected Result] + * @throws \Exception + */ + public function renderMessageFormatterDataProvider(): array + { + $twentynineteenJuneTwentyseven = new \DateTime('2019-06-27'); + + return [ + [ + 'A table has {legs, plural, =0 {no legs} =1 {one leg} other {# legs}}.', + 'en_US', + ['legs' => 4], + 'A table has 4 legs.' + ], + [ + 'A table has {legs, plural, =0 {no legs} =1 {one leg} other {# legs}}.', + 'en_US', + ['legs' => 0], + 'A table has no legs.' + ], + [ + 'A table has {legs, plural, =0 {no legs} =1 {one leg} other {# legs}}.', + 'en_US', + ['legs' => 1], + 'A table has one leg.' + ], + ['The table costs {price, number, currency}.', 'en_US', ['price' => 23.4], 'The table costs $23.40.'], + [ + 'Today is {date, date, long}.', + 'en_US', + ['date' => $twentynineteenJuneTwentyseven], + 'Today is June 27, 2019.' + ], + [ + 'Today is {date, date, long}.', + 'ja_JP', + ['date' => $twentynineteenJuneTwentyseven], + 'Today is 2019年6月27日.' + ], + ]; + } + + /** + * Test MessageFormatter + * + * @param string $text The text with MessageFormat markers + * @param string $locale + * @param array $arguments The arguments supplying values for the variables + * @param string $result The expected result of Phrase rendering + * + * @dataProvider renderMessageFormatterDataProvider + */ + public function testRenderMessageFormatter(string $text, string $locale, array $arguments, string $result): void + { + $renderer = $this->getMessageFormatter($locale); + + $this->assertEquals($result, $renderer->render([$text], $arguments)); + } + + /** + * Create a MessageFormatter object provided a locale + * + * Automatically sets up the Translate dependency to return the provided locale and returns a MessageFormatter + * that has been provided that dependency + * + * @param string $locale + * @return MessageFormatter + */ + private function getMessageFormatter(string $locale): MessageFormatter + { + $translateMock = $this->getMockBuilder(Translate::class) + ->disableOriginalConstructor() + ->setMethods(['getLocale']) + ->getMock(); + $translateMock->method('getLocale') + ->willReturn($locale); + + return $this->objectManager->getObject(MessageFormatter::class, ['translate' => $translateMock]); + } +} diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index c360d57be107f..80bf728e27ec3 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -16,6 +16,7 @@ "ext-gd": "*", "ext-hash": "*", "ext-iconv": "*", + "ext-intl": "*", "ext-openssl": "*", "ext-simplexml": "*", "ext-spl": "*", From 63063bcc6cb9a2343a906d0e05e3f7490b8a6a0d Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Tue, 14 May 2019 13:09:50 +0300 Subject: [PATCH 029/841] magento/magento2#22880 Fix random fails during parallel SCD execution No need to use locks when running in CLI mode --- .../Magento/Framework/View/Asset/LockerProcess.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Asset/LockerProcess.php b/lib/internal/Magento/Framework/View/Asset/LockerProcess.php index d320f951548b4..d74ee0490cdfb 100644 --- a/lib/internal/Magento/Framework/View/Asset/LockerProcess.php +++ b/lib/internal/Magento/Framework/View/Asset/LockerProcess.php @@ -62,7 +62,7 @@ public function __construct(Filesystem $filesystem) */ public function lockProcess($lockName) { - if ($this->getState()->getMode() == State::MODE_PRODUCTION) { + if ($this->getState()->getMode() === State::MODE_PRODUCTION || PHP_SAPI === 'cli') { return; } @@ -78,11 +78,12 @@ public function lockProcess($lockName) /** * @inheritdoc + * * @throws FileSystemException */ public function unlockProcess() { - if ($this->getState()->getMode() == State::MODE_PRODUCTION) { + if ($this->getState()->getMode() === State::MODE_PRODUCTION || PHP_SAPI === 'cli') { return; } @@ -115,9 +116,10 @@ private function isProcessLocked() } /** - * Get name of lock file + * Get path to lock file * * @param string $name + * * @return string */ private function getFilePath($name) @@ -126,7 +128,10 @@ private function getFilePath($name) } /** + * Get State object + * * @return State + * * @deprecated 100.1.1 */ private function getState() From 4a620bd1e6b9c21978c2c3e70d4d5338a71f676e Mon Sep 17 00:00:00 2001 From: Krissy Hiserote <khiserote@magento.com> Date: Fri, 24 May 2019 09:33:03 -0500 Subject: [PATCH 030/841] MC-16650: Product Attribute Type Price Not Displaying --- .../Model/Layer/Filter/Decimal.php | 8 +++-- .../Model/Search/RequestGenerator/Decimal.php | 33 ++++++++++++++++++- .../Search/RequestGenerator/DecimalTest.php | 22 +++++++++++-- 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php index e9fb1070fedd5..ea5492212c1f1 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogSearch\Model\Layer\Filter; use Magento\Catalog\Model\Layer\Filter\AbstractFilter; @@ -74,7 +76,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) ->getProductCollection() ->addFieldToFilter( $this->getAttributeModel()->getAttributeCode(), - ['from' => $from, 'to' => $to] + ['from' => $from, 'to' => empty($to) || $from === $to ? $to : $to - 0.001] ); $this->getLayer()->getState()->addFilter( @@ -111,7 +113,7 @@ protected function _getItemsData() $from = ''; } if ($to == '*') { - $to = null; + $to = ''; } $label = $this->renderRangeLabel(empty($from) ? 0 : $from, $to); $value = $from . '-' . $to; @@ -138,7 +140,7 @@ protected function _getItemsData() protected function renderRangeLabel($fromPrice, $toPrice) { $formattedFromPrice = $this->priceCurrency->format($fromPrice); - if ($toPrice === null) { + if ($toPrice === '') { return __('%1 and above', $formattedFromPrice); } else { if ($fromPrice != $toPrice) { diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php index b3d39a48fe9fc..c9f738b07c175 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php @@ -3,18 +3,36 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CatalogSearch\Model\Search\RequestGenerator; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\Search\Request\FilterInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory; +use Magento\Store\Model\ScopeInterface; /** * Catalog search range request generator. */ class Decimal implements GeneratorInterface { + /** + * @var \Magento\Store\Model\ScopeInterface + */ + private $scopeConfig; + + /** + * @param \Magento\Store\Model\ScopeInterface|null $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig = null) + { + $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); + } + /** * @inheritdoc */ @@ -38,8 +56,21 @@ public function getAggregationData(Attribute $attribute, $bucketName) 'type' => BucketInterface::TYPE_DYNAMIC, 'name' => $bucketName, 'field' => $attribute->getAttributeCode(), - 'method' => 'manual', + 'method' => $this->getRangeCalculation(), 'metric' => [['type' => 'count']], ]; } + + /** + * Get range calculation by what was set in the configuration + * + * @return string + */ + private function getRangeCalculation(): string + { + return $this->scopeConfig->getValue( + AlgorithmFactory::XML_PATH_RANGE_CALCULATION, + ScopeInterface::SCOPE_STORE + ); + } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php index 8157c1fa8fa82..f04188fbf7bdd 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CatalogSearch\Test\Unit\Model\Search\RequestGenerator; @@ -10,7 +11,11 @@ use Magento\CatalogSearch\Model\Search\RequestGenerator\Decimal; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\Search\Request\FilterInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +/** + * Test catalog search range request generator. + */ class DecimalTest extends \PHPUnit\Framework\TestCase { /** @var Decimal */ @@ -19,14 +24,23 @@ class DecimalTest extends \PHPUnit\Framework\TestCase /** @var Attribute|\PHPUnit_Framework_MockObject_MockObject */ private $attribute; + /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $scopeConfigMock; + protected function setUp() { $this->attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->setMethods(['getAttributeCode']) ->getMockForAbstractClass(); + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->setMethods(['getValue']) + ->getMockForAbstractClass(); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->decimal = $objectManager->getObject(Decimal::class); + $this->decimal = $objectManager->getObject( + Decimal::class, + ['scopeConfig' => $this->scopeConfigMock] + ); } public function testGetFilterData() @@ -51,16 +65,20 @@ public function testGetAggregationData() { $bucketName = 'test_bucket_name'; $attributeCode = 'test_attribute_code'; + $method = 'manual'; $expected = [ 'type' => BucketInterface::TYPE_DYNAMIC, 'name' => $bucketName, 'field' => $attributeCode, - 'method' => 'manual', + 'method' => $method, 'metric' => [['type' => 'count']], ]; $this->attribute->expects($this->atLeastOnce()) ->method('getAttributeCode') ->willReturn($attributeCode); + $this->scopeConfigMock->expects($this->once()) + ->method('getValue') + ->willReturn($method); $actual = $this->decimal->getAggregationData($this->attribute, $bucketName); $this->assertEquals($expected, $actual); } From d9e411305d1618c8d9e8a01444c8115ff804e9a5 Mon Sep 17 00:00:00 2001 From: Navarr Barnier <navarr@mediotype.com> Date: Fri, 24 May 2019 13:54:04 -0400 Subject: [PATCH 031/841] Update MessageFormatter formatting to align with code style * Declares strict types properly * @inheritDoc -> @inheritdoc * Simplifies FQNs --- .../Framework/Phrase/Renderer/MessageFormatter.php | 4 +++- .../Phrase/Test/Unit/Renderer/MessageFormatterTest.php | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php b/lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php index 575b37c264e87..13d8c85efe069 100644 --- a/lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php +++ b/lib/internal/Magento/Framework/Phrase/Renderer/MessageFormatter.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Phrase\Renderer; use Magento\Framework\Phrase\RendererInterface; @@ -26,7 +28,7 @@ public function __construct(TranslateInterface $translate) } /** - * @inheritDoc + * @inheritdoc */ public function render(array $source, array $arguments) { diff --git a/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php b/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php index f01dc6ef01b3f..aa71d801f35b6 100644 --- a/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php +++ b/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/MessageFormatterTest.php @@ -4,24 +4,30 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Phrase\Test\Unit\Renderer; use Magento\Framework\Phrase\Renderer\MessageFormatter; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\Translate; +use PHPUnit\Framework\TestCase; /** * Tests that messages sent through the MessageFormatter phrase renderer result in what would be expected when sent * through PHP's native MessageFormatter, and that the locale is pulled from the Translate dependency */ -class MessageFormatterTest extends \PHPUnit\Framework\TestCase +class MessageFormatterTest extends TestCase { /** @var ObjectManager */ private $objectManager; + /** + * @inheritdoc + */ protected function setUp() { - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->objectManager = new ObjectManager($this); } /** From e036a53959192bfeea21a8df7cc7b07891c71138 Mon Sep 17 00:00:00 2001 From: Kieu Phan <kphan@adobe.com> Date: Fri, 24 May 2019 14:33:27 -0500 Subject: [PATCH 032/841] MC-16650: Product Attribute Type Price Not Displaying Added MFTF tests --- .../Test/Mftf/Data/ProductAttributeData.xml | 22 +++++ .../Mftf/Section/AdminProductFormSection.xml | 1 + .../StorefrontCategoryFilterSection.xml | 1 + .../LayerNavigationOfCatalogSearchTest.xml | 85 +++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100644 app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index 817dd637f81dd..31a3745ca4417 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -278,6 +278,28 @@ <data key="used_for_sort_by">false</data> <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> </entity> + <entity name="productAttributePrice" type="ProductAttribute"> + <data key="attribute_code" unique="suffix">attribute</data> + <data key="frontend_input">price</data> + <data key="scope">global</data> + <data key="is_required">false</data> + <data key="is_unique">false</data> + <data key="is_searchable">false</data> + <data key="is_visible">true</data> + <data key="is_wysiwyg_enabled">false</data> + <data key="is_visible_in_advanced_search">false</data> + <data key="is_visible_on_front">true</data> + <data key="is_filterable">true</data> + <data key="is_filterable_in_search">false</data> + <data key="used_in_product_listing">false</data> + <data key="is_used_for_promo_rules">false</data> + <data key="is_comparable">true</data> + <data key="is_used_in_grid">false</data> + <data key="is_visible_in_grid">false</data> + <data key="is_filterable_in_grid">false</data> + <data key="used_for_sort_by">false</data> + <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> + </entity> <entity name="textProductAttribute" extends="productAttributeWysiwyg" type="ProductAttribute"> <data key="frontend_input">text</data> <data key="default_value" unique="suffix">defaultValue</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index f515171e835db..9ef3de033f808 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -207,5 +207,6 @@ <element name="textAttributeByName" type="text" selector="//div[@data-index='attributes']//fieldset[contains(@class, 'admin__field') and .//*[contains(.,'{{var}}')]]//input" parameterized="true"/> <element name="dropDownAttribute" type="select" selector="//select[@name='product[{{arg}}]']" parameterized="true" timeout="30"/> <element name="attributeSection" type="block" selector="//div[@data-index='attributes']/div[contains(@class, 'admin__collapsible-content _show')]" timeout="30"/> + <element name="customAttribute" type="text" selector="product[{{attributecode}}]" timeout="30" parameterized="true"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml index ddec4428f90e2..21a0d72dc991c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontCategoryFilterSection"> <element name="CategoryFilter" type="button" selector="//main//div[@class='filter-options']//div[contains(text(), 'Category')]"/> <element name="CategoryByName" type="button" selector="//main//div[@class='filter-options']//li[@class='item']//a[contains(text(), '{{var1}}')]" parameterized="true"/> + <element name="CustomPriceAttribute" type="button" selector="//div[contains(@class,'filter-content')]//div[text()='{{attributename}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml new file mode 100644 index 0000000000000..3bc086c62bb3b --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="LayerNavigationOfCatalogSearchTest"> + <annotations> + <stories value="Search terms"/> + <title value="Layer Navigation of Catalog Search Should Work As The Configuration"/> + <description value="Layer Navigation of Catalog Search Should Work As The Configuration"/> + <testCaseId value=""/> + <severity value="MAJOR"/> + <group value="CatalogSearch"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData stepKey="createPriceAttribute" entity="productAttributePrice"/> + <createData stepKey="assignPriceAttributeGroup" entity="AddToDefaultSet"> + <requiredEntity createDataKey="createPriceAttribute"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="subCategory"/> + <createData entity="SimpleProduct" stepKey="simpleProduct1"> + <requiredEntity createDataKey="subCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="simpleProduct2"> + <requiredEntity createDataKey="subCategory"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="subCategory"/> + <deleteData stepKey="deleteSimpleProduct1" createDataKey="simpleProduct1"/> + <deleteData stepKey="deleteSimpleProduct2" createDataKey="simpleProduct2"/> + <deleteData createDataKey="createPriceAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Update value for price attribute of Product 1--> + <comment userInput="Update value for price attribute of Product 1" stepKey="comment1"/> + <actionGroup ref="navigateToCreatedProductEditPage" stepKey="navigateToCreatedProductEditPage1"> + <argument name="product" value="$$simpleProduct1$$"/> + </actionGroup> + <fillField selector="{{AdminProductAttributeSection.customAttribute($$createPriceAttribute.attribute_code$$)}}" userInput="30" stepKey="fillCustomPrice1"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton1"/> + <waitForPageLoad stepKey="waitForSimpleProductSaved1"/> + <!--Update value for price attribute of Product 2--> + <comment userInput="Update value for price attribute of Product 1" stepKey="comment2"/> + <actionGroup ref="navigateToCreatedProductEditPage" stepKey="navigateToCreatedProductEditPage2"> + <argument name="product" value="$$simpleProduct2$$"/> + </actionGroup> + <fillField selector="{{AdminProductAttributeSection.customAttribute($$createPriceAttribute.attribute_code$$)}}" userInput="70" stepKey="fillCustomPrice2"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton2"/> + <waitForPageLoad stepKey="waitForSimpleProductSaved2"/> + <!--Navigate to category on Storefront--> + <comment userInput="Navigate to category on Storefront" stepKey="comment3"/> + <amOnPage url="{{StorefrontCategoryPage.url($$subCategory.name$$)}}" stepKey="goToCategoryStorefront"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <seeElement selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute($$createPriceAttribute.attribute_label$$)}}" stepKey="seePriceLayerNavigation"/> + </test> +</tests> + + + + + + + + + + + + + + + + + + + + + + From cdc1c77a11aa1ce77cb930267e141f96e8552e4a Mon Sep 17 00:00:00 2001 From: Kieu Phan <kphan@adobe.com> Date: Fri, 24 May 2019 17:48:46 -0500 Subject: [PATCH 033/841] MC-16650: Product Attribute Type Price Not Displaying Added assertion for attribute lable and set the default configuration in Admin --- .../Test/Mftf/Section/AdminProductFormSection.xml | 1 + .../Mftf/Section/StorefrontCategoryFilterSection.xml | 3 ++- .../Mftf/Test/LayerNavigationOfCatalogSearchTest.xml | 12 ++++++++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 9ef3de033f808..76659dfa1c896 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -208,5 +208,6 @@ <element name="dropDownAttribute" type="select" selector="//select[@name='product[{{arg}}]']" parameterized="true" timeout="30"/> <element name="attributeSection" type="block" selector="//div[@data-index='attributes']/div[contains(@class, 'admin__collapsible-content _show')]" timeout="30"/> <element name="customAttribute" type="text" selector="product[{{attributecode}}]" timeout="30" parameterized="true"/> + <element name="customAttributeLabel" type="text" selector=" div[data-index={{attributecode}}] > div > label > span" timeout="30" parameterized="true"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml index 21a0d72dc991c..31667c9b89935 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml @@ -11,6 +11,7 @@ <section name="StorefrontCategoryFilterSection"> <element name="CategoryFilter" type="button" selector="//main//div[@class='filter-options']//div[contains(text(), 'Category')]"/> <element name="CategoryByName" type="button" selector="//main//div[@class='filter-options']//li[@class='item']//a[contains(text(), '{{var1}}')]" parameterized="true"/> - <element name="CustomPriceAttribute" type="button" selector="//div[contains(@class,'filter-content')]//div[text()='{{attributename}}']" parameterized="true"/> + <element name="CustomPriceAttribute" type="button" selector=".filter-options-title"/> + <element name="CustomPriceAttributeContent" type="text" selector=".filter-options > div >div[data-role='content']"/> </section> </sections> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml index 3bc086c62bb3b..32d7ebcb1d7c5 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml @@ -11,13 +11,14 @@ <test name="LayerNavigationOfCatalogSearchTest"> <annotations> <stories value="Search terms"/> - <title value="Layer Navigation of Catalog Search Should Work As The Configuration"/> - <description value="Layer Navigation of Catalog Search Should Work As The Configuration"/> - <testCaseId value=""/> + <title value="Layer Navigation of Catalog Search Should Equalize Price Range As Default Configuration"/> + <description value="Make sure filter of custom attribute with type of price displays on storefront Catalog page and price range should respect the configuration in Admin site"/> + <testCaseId value="MC-16979"/> <severity value="MAJOR"/> <group value="CatalogSearch"/> </annotations> <before> + <magentoCLI command="config:set catalog/layered_navigation/price_range_calculation auto" stepKey="setAutoPriceRange"/> <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> <createData stepKey="createPriceAttribute" entity="productAttributePrice"/> <createData stepKey="assignPriceAttributeGroup" entity="AddToDefaultSet"> @@ -43,6 +44,7 @@ <actionGroup ref="navigateToCreatedProductEditPage" stepKey="navigateToCreatedProductEditPage1"> <argument name="product" value="$$simpleProduct1$$"/> </actionGroup> + <grabTextFrom selector="{{AdminProductAttributeSection.customAttributeLabel($$createPriceAttribute.attribute_code$$)}}" stepKey="grabAttributeLabel"/> <fillField selector="{{AdminProductAttributeSection.customAttribute($$createPriceAttribute.attribute_code$$)}}" userInput="30" stepKey="fillCustomPrice1"/> <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton1"/> <waitForPageLoad stepKey="waitForSimpleProductSaved1"/> @@ -58,7 +60,9 @@ <comment userInput="Navigate to category on Storefront" stepKey="comment3"/> <amOnPage url="{{StorefrontCategoryPage.url($$subCategory.name$$)}}" stepKey="goToCategoryStorefront"/> <waitForPageLoad stepKey="waitForPageLoad"/> - <seeElement selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute($$createPriceAttribute.attribute_label$$)}}" stepKey="seePriceLayerNavigation"/> + <see userInput="{$grabAttributeLabel}" selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute}}" stepKey="seePriceLayerNavigation"/> + <click selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute}}" stepKey="expandPriceFilter"/> + <seeElement selector="{{StorefrontCategoryFilterSection.CustomPriceAttributeContent}}" stepKey="seeFilterContent"/> </test> </tests> From 869bd7e0b863896add20dca29f729acdebd415ba Mon Sep 17 00:00:00 2001 From: Tan Sezer <t.sezer@youwe.nl> Date: Mon, 27 May 2019 10:03:41 +0200 Subject: [PATCH 034/841] - Removed "skip duplicate image for first product" fixing from VariationHandler - Added new plugin to clean tmp images after adding/updating configurations --- .../Plugin/CleanConfigurationTmpImages.php | 129 ++++++++++++++++++ .../Model/Product/VariationHandler.php | 4 - .../ConfigurableProduct/etc/adminhtml/di.xml | 1 + 3 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php new file mode 100644 index 0000000000000..2e62f041d8416 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php @@ -0,0 +1,129 @@ +<?php +/** + * Product initialization helper + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin; + +use Magento\Framework\App\Filesystem\DirectoryList; + +/** + * Class CleanConfigurationTmpImages + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CleanConfigurationTmpImages +{ + /** + * @var \Magento\MediaStorage\Helper\File\Storage\Database + */ + protected $fileStorageDb; + + /** + * @var \Magento\Catalog\Model\Product\Media\Config + */ + protected $mediaConfig; + + /** + * @var \Magento\Framework\Filesystem\Directory\WriteInterface + */ + protected $mediaDirectory; + + /** + * @var \Magento\Framework\App\RequestInterface + */ + protected $request; + + /** + * @param \Magento\Framework\App\RequestInterface $request + * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb + * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig + * @param \Magento\Framework\Filesystem $filesystem + * @throws \Magento\Framework\Exception\FileSystemException + */ + public function __construct( + \Magento\Framework\App\RequestInterface $request, + \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb, + \Magento\Catalog\Model\Product\Media\Config $mediaConfig, + \Magento\Framework\Filesystem $filesystem + ) { + $this->request = $request; + $this->fileStorageDb = $fileStorageDb; + $this->mediaConfig = $mediaConfig; + $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); + } + + /** + * Clean Tmp configurable images + * + * @param \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper $subject + * @param \Magento\Catalog\Model\Product $configurableProduct + * + * @return \Magento\Catalog\Model\Product + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterInitialize( + \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper $subject, + \Magento\Catalog\Model\Product $configurableProduct + ) { + + // Clean tmp + $configurations = $this->getConfigurations(); + foreach ($configurations as $variationId => $simpleProductData) { + if (!isset($simpleProductData['media_gallery']['images'])) { + continue; + } + + foreach ($simpleProductData['media_gallery']['images'] as $imageId => $image) { + $file = $this->getFilenameFromTmp($image['file']); + if ($this->fileStorageDb->checkDbUsage()) { + $filename = $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaShortUrl($file)); + $this->fileStorageDb->deleteFile($filename); + } else { + $filename = $this->mediaConfig->getTmpMediaPath($file); + $this->mediaDirectory->delete($filename); + } + } + } + + + return $configurableProduct; + } + + /** + * Trim .tmp ending from filename + * + * @param string $file + * @return string + */ + protected function getFilenameFromTmp($file) + { + return strrpos($file, '.tmp') == strlen($file) - 4 ? substr($file, 0, strlen($file) - 4) : $file; + } + + /** + * Get configurations from request + * + * @return array + */ + protected function getConfigurations() + { + $result = []; + $configurableMatrix = $this->request->getParam('configurable-matrix-serialized', "[]"); + if (isset($configurableMatrix) && $configurableMatrix != "") { + $configurableMatrix = json_decode($configurableMatrix, true); + + foreach ($configurableMatrix as $item) { + if (empty($item['was_changed']) && empty($item['newProduct'])) { + continue; + } + + $result[] = $item; + } + } + + return $result; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index 1bd8ef59f0d6d..c5a5dab2f1b68 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -238,10 +238,6 @@ public function duplicateImagesForVariations($productsData) foreach ($simpleProductData['media_gallery']['images'] as $imageId => $image) { $image['variation_id'] = $variationId; - if (isset($imagesForCopy[$imageId][0])) { - // skip duplicate image for first product - unset($imagesForCopy[$imageId][0]); - } $imagesForCopy[$imageId][] = $image; } } diff --git a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml index a401dceaf5b99..59f9204472b54 100644 --- a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml @@ -9,6 +9,7 @@ <type name="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper"> <plugin name="configurable" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\Configurable" sortOrder="50" /> <plugin name="updateConfigurations" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\UpdateConfigurations" sortOrder="60" /> + <plugin name="cleanConfigurationTmpImages" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\CleanConfigurationTmpImages" sortOrder="999" /> </type> <type name="Magento\Catalog\Controller\Adminhtml\Product\Builder"> <plugin name="configurable" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Builder\Plugin" sortOrder="50" /> From daa96f487090fce5bc077564edb33d97fc35fd63 Mon Sep 17 00:00:00 2001 From: Tan Sezer <t.sezer@youwe.nl> Date: Mon, 27 May 2019 13:54:07 +0200 Subject: [PATCH 035/841] - created test for CleanConfigurationTmpImages - added a missing param to function docs --- .../Plugin/CleanConfigurationTmpImages.php | 2 - .../CleanConfigurationTmpImagesTest.php | 220 ++++++++++++++++++ .../Plugin/UpdateConfigurationsTest.php | 1 + 3 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImagesTest.php diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php index 2e62f041d8416..d85049940d323 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php +++ b/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php @@ -69,7 +69,6 @@ public function afterInitialize( \Magento\Catalog\Model\Product $configurableProduct ) { - // Clean tmp $configurations = $this->getConfigurations(); foreach ($configurations as $variationId => $simpleProductData) { if (!isset($simpleProductData['media_gallery']['images'])) { @@ -88,7 +87,6 @@ public function afterInitialize( } } - return $configurableProduct; } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImagesTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImagesTest.php new file mode 100644 index 0000000000000..36bea34ef8452 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImagesTest.php @@ -0,0 +1,220 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Test\Unit\Controller\Adminhtml\Product\Initialization\Helper\Plugin; + +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper as ProductInitializationHelper; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Media\Config as MediaConfig; +use Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\CleanConfigurationTmpImages; +use Magento\Framework\App\RequestInterface; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\Write; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\MediaStorage\Helper\File\Storage\Database as FileStorage; + +/** + * Class CleanConfigurationTmpImagesTest + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @package Magento\ConfigurableProduct\Test\Unit\Controller\Adminhtml\Product\Initialization\Helper\Plugin + */ +class CleanConfigurationTmpImagesTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var CleanConfigurationTmpImages + */ + private $cleanConfigurationTmpImages; + + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + /** + * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var FileStorage|\PHPUnit_Framework_MockObject_MockObject + */ + private $fileStorageDb; + + /** + * @var MediaConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $mediaConfig; + + /** + * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject + */ + private $filesystem; + + /** + * @var Write|\PHPUnit_Framework_MockObject_MockObject + */ + private $writeFolder; + + /** + * @var ProductInitializationHelper|\PHPUnit_Framework_MockObject_MockObject + */ + private $subjectMock; + + protected function setUp() + { + $this->requestMock = $this->getMockBuilder(RequestInterface::class) + ->getMockForAbstractClass(); + $this->fileStorageDb = $this->getMockBuilder(FileStorage::class) + ->disableOriginalConstructor() + ->getMock(); + $this->mediaConfig = $this->getMockBuilder(MediaConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $this->filesystem = $this->getMockBuilder(Filesystem::class) + ->disableOriginalConstructor() + ->getMock(); + $this->writeFolder = $this->getMockBuilder(Write::class) + ->disableOriginalConstructor() + ->getMock(); + $this->subjectMock = $this->getMockBuilder(ProductInitializationHelper::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->filesystem->expects($this->once()) + ->method('getDirectoryWrite') + ->willReturn($this->writeFolder); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->cleanConfigurationTmpImages = $this->objectManagerHelper->getObject( + CleanConfigurationTmpImages::class, + [ + 'request' => $this->requestMock, + 'fileStorageDb' => $this->fileStorageDb, + 'mediaConfig' => $this->mediaConfig, + 'filesystem' => $this->filesystem + ] + ); + } + + /** + * Prepare configurable matrix + * + * @return array + */ + private function getConfigurableMatrix() + { + return [ + [ + 'newProduct' => true, + 'id' => 'product1' + ], + [ + 'newProduct' => false, + 'id' => 'product2', + 'status' => 'simple2_status', + 'sku' => 'simple2_sku', + 'name' => 'simple2_name', + 'price' => '3.33', + 'configurable_attribute' => 'simple2_configurable_attribute', + 'weight' => '5.55', + 'media_gallery' => [ + 'images' => [ + ['file' => 'test'] + ], + ], + 'swatch_image' => 'simple2_swatch_image', + 'small_image' => 'simple2_small_image', + 'thumbnail' => 'simple2_thumbnail', + 'image' => 'simple2_image', + 'was_changed' => true, + ], + [ + 'newProduct' => false, + 'id' => 'product3', + 'qty' => '3', + 'was_changed' => true, + ], + [ + 'newProduct' => false, + 'id' => 'product4', + 'status' => 'simple4_status', + 'sku' => 'simple2_sku', + 'name' => 'simple2_name', + 'price' => '3.33', + 'weight' => '5.55', + ], + ]; + } + + public function testAfterInitialize() + { + $productMock = $this->getProductMock(); + $configurableMatrix = $this->getConfigurableMatrix(); + + $this->requestMock->expects(static::any()) + ->method('getParam') + ->willReturnMap( + [ + ['store', 0, 0], + ['configurable-matrix-serialized', "[]", json_encode($configurableMatrix)] + ] + ); + + $this->assertSame($productMock, $this->cleanConfigurationTmpImages->afterInitialize($this->subjectMock, $productMock)); + } + + /** + * Get product mock + * + * @param array $expectedData + * @param bool $hasDataChanges + * @param bool $wasChanged + * @return Product|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getProductMock(array $expectedData = null, $hasDataChanges = false, $wasChanged = false) + { + $productMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->getMock(); + + if ($wasChanged !== false) { + if ($expectedData !== null) { + $productMock->expects(static::once()) + ->method('addData') + ->with($expectedData) + ->willReturnSelf(); + } + + $productMock->expects(static::any()) + ->method('hasDataChanges') + ->willReturn($hasDataChanges); + $productMock->expects($hasDataChanges ? static::once() : static::never()) + ->method('save') + ->willReturnSelf(); + } + return $productMock; + } + + /** + * Test for no exceptions if configurable matrix is empty string. + */ + public function testAfterInitializeEmptyMatrix() + { + $productMock = $this->getProductMock(); + + $this->requestMock->expects(static::any()) + ->method('getParam') + ->willReturnMap( + [ + ['store', 0, 0], + ['configurable-matrix-serialized', null, ''], + ] + ); + + $this->cleanConfigurationTmpImages->afterInitialize($this->subjectMock, $productMock); + + $this->assertEmpty($productMock->getData()); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php index fb63dc330db32..6bf36c2cf446f 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/UpdateConfigurationsTest.php @@ -185,6 +185,7 @@ public function testAfterInitialize() * * @param array $expectedData * @param bool $hasDataChanges + * @param bool $wasChanged * @return Product|\PHPUnit_Framework_MockObject_MockObject */ protected function getProductMock(array $expectedData = null, $hasDataChanges = false, $wasChanged = false) From 06926c83b5b99c115409cec4b9c19b3909c6ba4d Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Wed, 29 May 2019 11:52:34 +0300 Subject: [PATCH 036/841] MAGETWO-44170: Not pass function test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Unskip tests. --- .../TestCase/Product/ProductTypeSwitchingOnUpdateTest.php | 1 + .../TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php index 90cd6bdb76328..2abd17fce5b45 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php @@ -36,6 +36,7 @@ class ProductTypeSwitchingOnUpdateTest extends Injectable { /* tags */ + const TEST_TYPE = 'acceptance_test'; const MVP = 'yes'; /* end tags */ diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml index 5fa1cfe5e5911..3d3e36754e92c 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml @@ -11,6 +11,7 @@ <data name="productOrigin" xsi:type="string">catalogProductSimple::default</data> <data name="product" xsi:type="string">configurableProduct::default</data> <data name="actionName" xsi:type="string">-</data> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductsInGrid" /> @@ -34,6 +35,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation4"> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <data name="productOrigin" xsi:type="string">configurableProduct::default</data> <data name="product" xsi:type="string">catalogProductVirtual::required_fields</data> <data name="actionName" xsi:type="string">deleteVariations</data> @@ -48,6 +50,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation6"> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <data name="productOrigin" xsi:type="string">catalogProductVirtual::default</data> <data name="product" xsi:type="string">configurableProduct::not_virtual_for_type_switching</data> <data name="actionName" xsi:type="string">-</data> @@ -60,6 +63,7 @@ <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductIsNotDisplayedSeparately" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation7"> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <data name="productOrigin" xsi:type="string">catalogProductVirtual::default</data> <data name="product" xsi:type="string">downloadableProduct::default</data> <data name="actionName" xsi:type="string">-</data> @@ -71,6 +75,7 @@ <constraint name="Magento\Downloadable\Test\Constraint\AssertDownloadableLinksData" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation8"> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <data name="productOrigin" xsi:type="string">downloadableProduct::default</data> <data name="product" xsi:type="string">catalogProductSimple::default</data> <data name="actionName" xsi:type="string">-</data> @@ -78,6 +83,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation9"> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <data name="productOrigin" xsi:type="string">downloadableProduct::default</data> <data name="product" xsi:type="string">configurableProduct::not_virtual_for_type_switching</data> <data name="actionName" xsi:type="string">clearDownloadableData</data> @@ -97,6 +103,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation11"> + <data name="tag" xsi:type="string">test_type:acceptance_test</data> <data name="productOrigin" xsi:type="string">catalogProductSimple::default</data> <data name="product" xsi:type="string">downloadableProduct::default</data> <data name="actionName" xsi:type="string">-</data> From ea64edf1b2c274b407312adc8ce8ea3e343d0b91 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Wed, 29 May 2019 14:51:39 +0300 Subject: [PATCH 037/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix visibility and category filters; - Add integration tests. --- .../ResourceModel/Product/Collection.php | 48 ++++++++++- .../ResourceModel/Product/CollectionTest.php | 82 +++++++++++++++++-- 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 384b6ddcefc31..cabbcb67fb5a7 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Model\ResourceModel\Product; @@ -22,6 +23,7 @@ use Magento\Framework\Indexer\DimensionFactory; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\Store; +use Magento\Catalog\Api\CategoryRepositoryInterface; /** * Product collection @@ -302,6 +304,11 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ private $urlFinder; + /** + * @var CategoryRepositoryInterface + */ + private $categoryRepository; + /** * Collection constructor * @@ -330,6 +337,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param TableMaintainer|null $tableMaintainer * @param PriceTableResolver|null $priceTableResolver * @param DimensionFactory|null $dimensionFactory + * @param CategoryRepositoryInterface|null $categoryRepository * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -358,7 +366,8 @@ public function __construct( MetadataPool $metadataPool = null, TableMaintainer $tableMaintainer = null, PriceTableResolver $priceTableResolver = null, - DimensionFactory $dimensionFactory = null + DimensionFactory $dimensionFactory = null, + CategoryRepositoryInterface $categoryRepository = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -392,6 +401,8 @@ public function __construct( $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class); $this->dimensionFactory = $dimensionFactory ?: ObjectManager::getInstance()->get(DimensionFactory::class); + $this->categoryRepository = $categoryRepository ?: ObjectManager::getInstance() + ->get(CategoryRepositoryInterface::class); } /** @@ -1673,7 +1684,11 @@ public function addFilterByRequiredOptions() public function setVisibility($visibility) { $this->_productLimitationFilters['visibility'] = $visibility; - $this->_applyProductLimitations(); + if ($this->getStoreId() == Store::DEFAULT_STORE_ID) { + $this->addAttributeToFilter('visibility', $visibility); + } else { + $this->_applyProductLimitations(); + } return $this; } @@ -2053,12 +2068,14 @@ protected function _applyProductLimitations() protected function _applyZeroStoreProductLimitations() { $filters = $this->_productLimitationFilters; + $categories = []; + $categories = $this->getChildrenCategories((int)$filters['category_id'], $categories); $conditions = [ 'cat_pro.product_id=e.entity_id', $this->getConnection()->quoteInto( - 'cat_pro.category_id=?', - $filters['category_id'] + 'cat_pro.category_id IN (?)', + $categories ), ]; $joinCond = join(' AND ', $conditions); @@ -2079,6 +2096,29 @@ protected function _applyZeroStoreProductLimitations() return $this; } + /** + * Get children categories. + * + * @param int $categoryId + * @param array $categories + * @return array + */ + private function getChildrenCategories(int $categoryId, array $categories): array + { + $category = $this->categoryRepository->get($categoryId); + $categories[] = $category->getId(); + if ($category->getIsAnchor()) { + $categoryChildren = $category->getChildren(); + $categoryChildrenIds = explode(',', $categoryChildren); + foreach ($categoryChildrenIds as $categoryChildrenId) { + if ($categoryChildrenId) { + $categories = $this->getChildrenCategories((int)$categoryChildrenId, $categories); + } + } + } + return $categories; + } + /** * Add category ids to loaded items * diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php index 4cc6265a992fa..80d700c7e54ff 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php @@ -3,8 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\ResourceModel\Product; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\App\Area; +use Magento\Framework\App\State; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; + /** * Collection test */ @@ -31,15 +39,15 @@ class CollectionTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->collection = Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\ResourceModel\Product\Collection::class ); - $this->processor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->processor = Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\Indexer\Product\Price\Processor::class ); - $this->productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->productRepository = Bootstrap::getObjectManager()->create( \Magento\Catalog\Api\ProductRepositoryInterface::class ); } @@ -54,7 +62,7 @@ public function testAddPriceDataOnSchedule() $this->processor->getIndexer()->setScheduled(true); $this->assertTrue($this->processor->getIndexer()->isScheduled()); - $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + $productRepository = Bootstrap::getObjectManager() ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ $product = $productRepository->get('simple'); @@ -73,7 +81,7 @@ public function testAddPriceDataOnSchedule() //reindexing $this->processor->getIndexer()->reindexList([1]); - $this->collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->collection = Bootstrap::getObjectManager()->create( \Magento\Catalog\Model\ResourceModel\Product\Collection::class ); $this->collection->addPriceData(0, 1); @@ -89,6 +97,66 @@ public function testAddPriceDataOnSchedule() $this->processor->getIndexer()->setScheduled(false); } + /** + * @magentoDataFixture Magento/Catalog/_files/products.php + * @magentoDbIsolation disabled + */ + public function testSetVisibility() + { + $appState = Bootstrap::getObjectManager() + ->create(State::class); + $appState->setAreaCode(Area::AREA_CRONTAB); + $this->collection->setStoreId(Store::DEFAULT_STORE_ID); + $this->collection->setVisibility([Visibility::VISIBILITY_BOTH]); + $this->collection->load(); + /** @var \Magento\Catalog\Api\Data\ProductInterface[] $product */ + $items = $this->collection->getItems(); + $this->assertCount(2, $items); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/category_product.php + * @magentoDbIsolation disabled + */ + public function testSetCategoryWithStoreFilter() + { + $appState = Bootstrap::getObjectManager() + ->create(State::class); + $appState->setAreaCode(Area::AREA_CRONTAB); + + $category = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Catalog\Model\Category::class + )->load(333); + $this->collection->addCategoryFilter($category)->addStoreFilter(1); + $this->collection->load(); + + $collectionStoreFilterAfter = Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Product\Collection::class + ); + $collectionStoreFilterAfter->addStoreFilter(1)->addCategoryFilter($category); + $collectionStoreFilterAfter->load(); + $this->assertEquals($this->collection->getItems(), $collectionStoreFilterAfter->getItems()); + $this->assertCount(1, $collectionStoreFilterAfter->getItems()); + } + + /** + * @magentoDataFixture Magento/Catalog/_files/categories.php + * @magentoDbIsolation disabled + */ + public function testSetCategoryFilter() + { + $appState = Bootstrap::getObjectManager() + ->create(State::class); + $appState->setAreaCode(Area::AREA_CRONTAB); + + $category = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Catalog\Model\Category::class + )->load(3); + $this->collection->addCategoryFilter($category); + $this->collection->load(); + $this->assertEquals($this->collection->getSize(), 3); + } + /** * @magentoDataFixture Magento/Catalog/_files/products.php * @magentoAppIsolation enabled @@ -98,7 +166,7 @@ public function testAddPriceDataOnSave() { $this->processor->getIndexer()->setScheduled(false); $this->assertFalse($this->processor->getIndexer()->isScheduled()); - $productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + $productRepository = Bootstrap::getObjectManager() ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ $product = $productRepository->get('simple'); @@ -184,7 +252,7 @@ public function testJoinTable() $productTable = $this->collection->getTable('catalog_product_entity'); $urlRewriteTable = $this->collection->getTable('url_rewrite'); - // phpcs:ignore + // phpcs:ignore Magento2.SQL.RawQuery $expected = 'SELECT `e`.*, `alias`.`request_path` FROM `' . $productTable . '` AS `e`' . ' LEFT JOIN `' . $urlRewriteTable . '` AS `alias` ON (alias.entity_id =e.entity_id)' . ' AND (alias.entity_type = \'product\')'; From 2358d1c605472e862ae05a539dd9a220f9503567 Mon Sep 17 00:00:00 2001 From: Kieu Phan <kphan@adobe.com> Date: Fri, 31 May 2019 15:00:47 -0500 Subject: [PATCH 038/841] MC-16650: Product Attribute Type Price Not Displaying Changes entity name for MFTF test code --- .../Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml | 2 +- .../Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index 31a3745ca4417..ca2637e9314c7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -278,7 +278,7 @@ <data key="used_for_sort_by">false</data> <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> </entity> - <entity name="productAttributePrice" type="ProductAttribute"> + <entity name="productAttributeTypeOfPrice" type="ProductAttribute"> <data key="attribute_code" unique="suffix">attribute</data> <data key="frontend_input">price</data> <data key="scope">global</data> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml index 32d7ebcb1d7c5..cc414a1088ecb 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml @@ -19,8 +19,7 @@ </annotations> <before> <magentoCLI command="config:set catalog/layered_navigation/price_range_calculation auto" stepKey="setAutoPriceRange"/> - <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> - <createData stepKey="createPriceAttribute" entity="productAttributePrice"/> + <createData stepKey="createPriceAttribute" entity="productAttributeTypeOfPrice"/> <createData stepKey="assignPriceAttributeGroup" entity="AddToDefaultSet"> <requiredEntity createDataKey="createPriceAttribute"/> </createData> @@ -31,6 +30,7 @@ <createData entity="SimpleProduct" stepKey="simpleProduct2"> <requiredEntity createDataKey="subCategory"/> </createData> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> <deleteData stepKey="deleteSimpleSubCategory" createDataKey="subCategory"/> From 15d33b8b1c9633d0a96c244c20f10abcf50823c4 Mon Sep 17 00:00:00 2001 From: Kieu Phan <kphan@adobe.com> Date: Mon, 3 Jun 2019 16:44:46 -0500 Subject: [PATCH 039/841] MC-16650: Product Attribute Type Price Not Displaying Changed selector of grabTextFrom created attribute and removed the last assertion which is not scope of this test --- .../Test/Mftf/Section/StorefrontCategoryFilterSection.xml | 3 +-- .../Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml index 31667c9b89935..fe3d3e298cbc9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml @@ -11,7 +11,6 @@ <section name="StorefrontCategoryFilterSection"> <element name="CategoryFilter" type="button" selector="//main//div[@class='filter-options']//div[contains(text(), 'Category')]"/> <element name="CategoryByName" type="button" selector="//main//div[@class='filter-options']//li[@class='item']//a[contains(text(), '{{var1}}')]" parameterized="true"/> - <element name="CustomPriceAttribute" type="button" selector=".filter-options-title"/> - <element name="CustomPriceAttributeContent" type="text" selector=".filter-options > div >div[data-role='content']"/> + <element name="CustomPriceAttribute" type="button" selector="//div[contains(@class,'filter-options-title')]"/> </section> </sections> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml index cc414a1088ecb..f1c655f4b5ab1 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml @@ -14,6 +14,7 @@ <title value="Layer Navigation of Catalog Search Should Equalize Price Range As Default Configuration"/> <description value="Make sure filter of custom attribute with type of price displays on storefront Catalog page and price range should respect the configuration in Admin site"/> <testCaseId value="MC-16979"/> + <issueId value="MC-16650"/> <severity value="MAJOR"/> <group value="CatalogSearch"/> </annotations> @@ -44,7 +45,7 @@ <actionGroup ref="navigateToCreatedProductEditPage" stepKey="navigateToCreatedProductEditPage1"> <argument name="product" value="$$simpleProduct1$$"/> </actionGroup> - <grabTextFrom selector="{{AdminProductAttributeSection.customAttributeLabel($$createPriceAttribute.attribute_code$$)}}" stepKey="grabAttributeLabel"/> + <grabTextFrom selector="{{AdminProductFormSection.attributeLabelByText($$createPriceAttribute.attribute[frontend_labels][0][label]$$)}}" stepKey="grabAttributeLabel"/> <fillField selector="{{AdminProductAttributeSection.customAttribute($$createPriceAttribute.attribute_code$$)}}" userInput="30" stepKey="fillCustomPrice1"/> <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton1"/> <waitForPageLoad stepKey="waitForSimpleProductSaved1"/> @@ -61,8 +62,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$subCategory.name$$)}}" stepKey="goToCategoryStorefront"/> <waitForPageLoad stepKey="waitForPageLoad"/> <see userInput="{$grabAttributeLabel}" selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute}}" stepKey="seePriceLayerNavigation"/> - <click selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute}}" stepKey="expandPriceFilter"/> - <seeElement selector="{{StorefrontCategoryFilterSection.CustomPriceAttributeContent}}" stepKey="seeFilterContent"/> + <click selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute($grabAttributeLabel)}}" stepKey="expandPriceFilter"/> </test> </tests> From 14e6b3fc9c95023c00c670ae349de31e5a589994 Mon Sep 17 00:00:00 2001 From: Kieu Phan <kphan@adobe.com> Date: Mon, 3 Jun 2019 16:50:21 -0500 Subject: [PATCH 040/841] MC-16650: Product Attribute Type Price Not Displaying Removed unnecessary selector after changing assertion in test --- .../Catalog/Test/Mftf/Section/AdminProductFormSection.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 76659dfa1c896..9ef3de033f808 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -208,6 +208,5 @@ <element name="dropDownAttribute" type="select" selector="//select[@name='product[{{arg}}]']" parameterized="true" timeout="30"/> <element name="attributeSection" type="block" selector="//div[@data-index='attributes']/div[contains(@class, 'admin__collapsible-content _show')]" timeout="30"/> <element name="customAttribute" type="text" selector="product[{{attributecode}}]" timeout="30" parameterized="true"/> - <element name="customAttributeLabel" type="text" selector=" div[data-index={{attributecode}}] > div > label > span" timeout="30" parameterized="true"/> </section> </sections> \ No newline at end of file From a4f2c53f19359fd4b942e3609bfc314c07404780 Mon Sep 17 00:00:00 2001 From: Kieu Phan <kphan@adobe.com> Date: Mon, 3 Jun 2019 17:01:12 -0500 Subject: [PATCH 041/841] MC-16650: Product Attribute Type Price Not Displaying --- .../Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml index f1c655f4b5ab1..e91fb9b7a55cc 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml @@ -61,8 +61,7 @@ <comment userInput="Navigate to category on Storefront" stepKey="comment3"/> <amOnPage url="{{StorefrontCategoryPage.url($$subCategory.name$$)}}" stepKey="goToCategoryStorefront"/> <waitForPageLoad stepKey="waitForPageLoad"/> - <see userInput="{$grabAttributeLabel}" selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute}}" stepKey="seePriceLayerNavigation"/> - <click selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute($grabAttributeLabel)}}" stepKey="expandPriceFilter"/> + <see userInput="{$grabAttributeLabel}" selector="{{StorefrontCategoryFilterSection.CustomPriceAttribute}}" stepKey="seePriceLayerNavigationOnStorefront"/> </test> </tests> From 18a6653e3fa419331c1909dfdb38ec8e402d1502 Mon Sep 17 00:00:00 2001 From: IvanPletnyov <ivan.pletnyov@transoftgroup.com> Date: Tue, 4 Jun 2019 15:02:46 +0300 Subject: [PATCH 042/841] pull request 22123: Fix database compare test --- .../Data/UpdateCreditmemoGridCurrencyCode.php | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php diff --git a/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php b/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php new file mode 100644 index 0000000000000..7db6a01d98b73 --- /dev/null +++ b/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Setup\Patch\Data; + +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Setup\Patch\PatchVersionInterface; +use Magento\Sales\Setup\SalesSetupFactory; + +class UpdateCreditmemoGridCurrencyCode implements DataPatchInterface, PatchVersionInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var SalesSetupFactory + */ + private $salesSetupFactory; + + /** + * @param ModuleDataSetupInterface $moduleDataSetup + * @param SalesSetupFactory $salesSetupFactory + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + SalesSetupFactory $salesSetupFactory + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->salesSetupFactory = $salesSetupFactory; + } + + /** + * @inheritdoc + */ + public function apply() + { + $salesSetup = $this->salesSetupFactory->create(['setup' => $this->moduleDataSetup]); + $connection = $salesSetup->getConnection(); + $creditMemoGridTable = $salesSetup->getTable('sales_creditmemo_grid'); + $orderTable = $salesSetup->getTable('sales_order'); + + $sql = "UPDATE {$creditMemoGridTable} AS scg + JOIN {$orderTable} AS so ON so.entity_id = scg.order_id + SET scg.order_currency_code = so.order_currency_code, + scg.base_currency_code = so.base_currency_code;"; + + $connection->query($sql); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public static function getVersion() + { + return '2.0.13'; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} From 8f9f248cd73446c47515ad738022f08b977ad2ff Mon Sep 17 00:00:00 2001 From: Krissy Hiserote <khiserote@magento.com> Date: Tue, 4 Jun 2019 11:35:21 -0500 Subject: [PATCH 043/841] MC-16650: Product Attribute Type Price Not Displaying - extract hard to read logic and add integration tests --- .../Model/Layer/Filter/Decimal.php | 21 ++++- .../attribute_special_price_filterable.php | 12 +++ .../_files/multiple_visible_products.php | 76 +++++++++++++++++++ .../multiple_visible_products_rollback.php | 28 +++++++ .../Model/Layer/Filter/DecimalTest.php | 74 ++++++++++++++++++ 5 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php index ea5492212c1f1..4c83c3f7184f1 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php @@ -76,7 +76,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) ->getProductCollection() ->addFieldToFilter( $this->getAttributeModel()->getAttributeCode(), - ['from' => $from, 'to' => empty($to) || $from === $to ? $to : $to - 0.001] + ['from' => $from, 'to' => $this->getToRangeValue($from, $to)] ); $this->getLayer()->getState()->addFilter( @@ -149,4 +149,23 @@ protected function renderRangeLabel($fromPrice, $toPrice) return __('%1 - %2', $formattedFromPrice, $this->priceCurrency->format($toPrice)); } } + + /** + * Get the to range value + * + * When the range is 10-20 we only need to get products that are in the 10-19.99 range. + * 20 should be in the next range group. + * + * @param float|string $from + * @param float|string $to + * @return float|string + */ + private function getToRangeValue($from, $to) + { + if (!empty($to) && $from !== $to) { + $to -= 0.001; + } + + return $to; + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php new file mode 100644 index 0000000000000..3c92db99cd4f1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Setup\CategorySetup::class +); + +$installer->updateAttribute('catalog_product', 'special_price', 'is_filterable', 1); \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php new file mode 100644 index 0000000000000..4ace41d23c872 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId(10) + ->setAttributeSetId(4) + ->setName('Simple Product1') + ->setSku('simple1') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_IN_CART) + ->setPrice(15) + ->setWeight(10) + ->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) + ->setWebsiteIds([1]) + ->setCategoryIds([2]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setSpecialPrice('10') + ->save(); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId(11) + ->setAttributeSetId(4) + ->setName('Simple Product2') + ->setSku('simple2') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setOptionsContainer('container1') + ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_ON_GESTURE) + ->setPrice(25) + ->setWeight(20) + ->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) + ->setWebsiteIds([1]) + ->setCategoryIds([2]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setSpecialPrice('20') + ->save(); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId(12) + ->setAttributeSetId(4) + ->setName('Simple Product3') + ->setSku('simple3') + ->setTaxClassId('none') + ->setDescription('description') + ->setShortDescription('short description') + ->setPrice(35) + ->setWeight(30) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setWebsiteIds([1]) + ->setCategoryIds([2]) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) + ->setSpecialPrice('30') + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php new file mode 100644 index 0000000000000..a9155d3fadf0b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +foreach (['simple1', 'simple2', 'simple3'] as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + $productRepository->delete($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php index f0c8402c51879..33dea3ea37179 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php @@ -49,6 +49,80 @@ protected function setUp() $this->_model->setAttributeModel($attribute); } + /** + * Test the product collection returns the correct number of items after the filter is applied. + * + * @magentoDataFixture Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php + * @magentoDataFixture Magento/Catalog/_files/multiple_visible_products.php + * @magentoDbIsolation disabled + */ + public function testApplyProductCollection() + { + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $category = $objectManager->create(\Magento\Catalog\Model\Category::class); + $category->load(2); + $this->_model->getLayer()->setCurrentCategory($category); + + /** @var $attribute \Magento\Catalog\Model\Entity\Attribute */ + $attribute = $objectManager->create(\Magento\Catalog\Model\Entity\Attribute::class); + $attribute->loadByCode('catalog_product', 'special_price'); + $this->_model->setAttributeModel($attribute); + + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var $request \Magento\TestFramework\Request */ + $request = $objectManager->get(\Magento\TestFramework\Request::class); + $request->setParam('special_price', '10-20'); + $result = $this->_model->apply($request); + $collection = $this->_model->getLayer()->getProductCollection(); + $size = $collection->getSize(); + $this->assertEquals( + 1, + $size + ); + } + + /** + * Test the filter label is correct + */ + public function testApplyFilterLabel() + { + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var $request \Magento\TestFramework\Request */ + $request = $objectManager->get(\Magento\TestFramework\Request::class); + $request->setParam('weight', '10-20'); + $this->_model->apply($request); + + $filters = $this->_model->getLayer()->getState()->getFilters(); + $this->assertArrayHasKey(0, $filters); + $this->assertEquals( + '<span class="price">$10.00</span> - <span class="price">$19.99</span>', + (string)$filters[0]->getLabel() + ); + } + + /** + * Test the filter label is correct when there is empty To value + */ + public function testApplyFilterLabelWithEmptyToValue() + { + /** @var $objectManager \Magento\TestFramework\ObjectManager */ + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var $request \Magento\TestFramework\Request */ + $request = $objectManager->get(\Magento\TestFramework\Request::class); + $request->setParam('weight', '10-'); + $this->_model->apply($request); + + $filters = $this->_model->getLayer()->getState()->getFilters(); + $this->assertArrayHasKey(0, $filters); + $this->assertEquals( + '<span class="price">$10.00</span> and above', + (string)$filters[0]->getLabel() + ); + } + public function testApplyNothing() { $this->assertEmpty($this->_model->getData('range')); From ae304acdf0cf567add3b5d501a64fe4605877574 Mon Sep 17 00:00:00 2001 From: Nazarn96 <nazarn96@gmail.com> Date: Wed, 5 Jun 2019 10:59:50 +0300 Subject: [PATCH 044/841] magento/magento#23005 static-functional-test-fix --- .../Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php | 5 ++++- .../Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml | 4 ++-- .../app/Magento/Sales/Test/Block/Adminhtml/Invoice/Grid.php | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php b/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php index 7db6a01d98b73..0b422c2f43995 100644 --- a/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php +++ b/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php @@ -12,6 +12,9 @@ use Magento\Framework\Setup\Patch\PatchVersionInterface; use Magento\Sales\Setup\SalesSetupFactory; +/** + * Update credit memo grid currency code. + */ class UpdateCreditmemoGridCurrencyCode implements DataPatchInterface, PatchVersionInterface { /** @@ -45,7 +48,7 @@ public function apply() $connection = $salesSetup->getConnection(); $creditMemoGridTable = $salesSetup->getTable('sales_creditmemo_grid'); $orderTable = $salesSetup->getTable('sales_order'); - + // phpcs:disable Magento2.SQL.RawQuery $sql = "UPDATE {$creditMemoGridTable} AS scg JOIN {$orderTable} AS so ON so.entity_id = scg.order_id SET scg.order_currency_code = so.order_currency_code, diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml index f63979c4ac54b..88d90bc716576 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderInvoicesTabSection.xml @@ -17,7 +17,7 @@ <element name="filters" type="button" selector="//div[@id='sales_order_view_tabs_order_invoices_content']//button[@data-action='grid-filter-expand']" timeout="30"/> <element name="applyFilters" type="button" selector="//div[@id='sales_order_view_tabs_order_invoices_content']//button[@data-action='grid-filter-apply']" timeout="30"/> <element name="invoiceId" type="input" selector="//div[@id='sales_order_view_tabs_order_invoices_content']//input[@name='increment_id']" timeout="30"/> - <element name="amountFrom" type="input" selector="[name='grand_total[from]']" timeout="30"/> - <element name="amountTo" type="input" selector="[name='grand_total[to]']" timeout="30"/> + <element name="amountFrom" type="input" selector="[name='base_grand_total[from]']" timeout="30"/> + <element name="amountTo" type="input" selector="[name='base_grand_total[to]']" timeout="30"/> </section> </sections> \ No newline at end of file diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Invoice/Grid.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Invoice/Grid.php index 4d37ebe95a7ec..a5c172da3a992 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Invoice/Grid.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Block/Adminhtml/Invoice/Grid.php @@ -24,10 +24,10 @@ class Grid extends \Magento\Ui\Test\Block\Adminhtml\DataGrid 'selector' => 'input[name="order_increment_id"]', ], 'grand_total_from' => [ - 'selector' => 'input[name="grand_total[from]"]', + 'selector' => 'input[name="base_grand_total[from]"]', ], 'grand_total_to' => [ - 'selector' => 'input[name="grand_total[to]"]', + 'selector' => 'input[name="base_grand_total[to]"]', ], ]; From cf2eb43d75d7e5004f18bbf55483f2787553171b Mon Sep 17 00:00:00 2001 From: Evgeny Petrov <evgeny_petrov@epam.com> Date: Fri, 7 Jun 2019 14:25:05 +0300 Subject: [PATCH 045/841] MC-15341: Default product numbers to display results in poor display on Desktop --- app/code/Magento/Catalog/etc/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 3a842166a3825..a9ed43fae1d16 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -23,7 +23,7 @@ </fields_masks> <frontend> <list_mode>grid-list</list_mode> - <grid_per_page_values>9,15,30</grid_per_page_values> + <grid_per_page_values>12,24,36</grid_per_page_values> <list_per_page_values>5,10,15,20,25</list_per_page_values> <grid_per_page>9</grid_per_page> <list_per_page>10</list_per_page> From fdc9e9d9a5c92af0c50f1d4c3c4dffc6d61d7209 Mon Sep 17 00:00:00 2001 From: Evgeny Petrov <evgeny_petrov@epam.com> Date: Mon, 10 Jun 2019 14:13:09 +0300 Subject: [PATCH 046/841] MC-15341: Default product numbers to display results in poor display on Desktop --- app/code/Magento/Catalog/etc/config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index a9ed43fae1d16..20511f4ff2295 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -25,7 +25,7 @@ <list_mode>grid-list</list_mode> <grid_per_page_values>12,24,36</grid_per_page_values> <list_per_page_values>5,10,15,20,25</list_per_page_values> - <grid_per_page>9</grid_per_page> + <grid_per_page>12</grid_per_page> <list_per_page>10</list_per_page> <flat_catalog_category>0</flat_catalog_category> <default_sort_by>position</default_sort_by> From 4bda471ee4bb32a109bdf564afe7716ebb79d767 Mon Sep 17 00:00:00 2001 From: Yurii Sapiha <yurasapiga93@gmail.com> Date: Tue, 11 Jun 2019 12:16:45 +0300 Subject: [PATCH 047/841] fix-patch magento/magento2#22123 --- .../Data/UpdateCreditmemoGridCurrencyCode.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php b/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php index 0b422c2f43995..b143ae7c3ba7f 100644 --- a/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php +++ b/app/code/Magento/Sales/Setup/Patch/Data/UpdateCreditmemoGridCurrencyCode.php @@ -7,9 +7,11 @@ namespace Magento\Sales\Setup\Patch\Data; +use Magento\Framework\DB\Adapter\Pdo\Mysql; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; +use Magento\Sales\Setup\SalesSetup; use Magento\Sales\Setup\SalesSetupFactory; /** @@ -44,16 +46,16 @@ public function __construct( */ public function apply() { + /** @var SalesSetup $salesSetup */ $salesSetup = $this->salesSetupFactory->create(['setup' => $this->moduleDataSetup]); + /** @var Mysql $connection */ $connection = $salesSetup->getConnection(); $creditMemoGridTable = $salesSetup->getTable('sales_creditmemo_grid'); $orderTable = $salesSetup->getTable('sales_order'); - // phpcs:disable Magento2.SQL.RawQuery - $sql = "UPDATE {$creditMemoGridTable} AS scg - JOIN {$orderTable} AS so ON so.entity_id = scg.order_id - SET scg.order_currency_code = so.order_currency_code, - scg.base_currency_code = so.base_currency_code;"; - + $select = $connection->select(); + $condition = 'so.entity_id = scg.order_id'; + $select->join(['so' => $orderTable], $condition, ['order_currency_code', 'base_currency_code']); + $sql = $connection->updateFromSelect($select, ['scg' => $creditMemoGridTable]); $connection->query($sql); } From b22d5d11a2633cad3f5f9ee4e96b546d21db0c43 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Wed, 12 Jun 2019 13:12:46 +0400 Subject: [PATCH 048/841] MC-17007: "Set Active" action on customers grid does not work properly - Added automated test script. --- .../Mftf/ActionGroup/AdminCustomerGridActionGroup.xml | 11 +++++++++++ .../AdminEditCustomerInformationFromActionGroup.xml | 8 ++++++++ .../Section/AdminCustomerGridMainActionsSection.xml | 1 + .../Test/Mftf/Section/AdminCustomerGridSection.xml | 3 +++ 4 files changed, 23 insertions(+) diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml index 86039056999b0..a247415664d30 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml @@ -20,4 +20,15 @@ <click selector="{{AdminCustomerFiltersSection.apply}}" stepKey="applyFilter"/> <waitForPageLoad stepKey="waitForPageToLoad"/> </actionGroup> + <actionGroup name="adminSetCustomerActiveViaGrid"> + <arguments> + <argument name="customerEmail" type="string" defaultValue="{{Simple_US_CA_Customer.email}}"/> + </arguments> + <click selector="{{AdminCustomerGridMainActionsSection.customerCheckbox(customerEmail)}}" stepKey="chooseCustomer" /> + <click selector="{{AdminCustomerGridMainActionsSection.actions}}" stepKey="openActions"/> + <waitForElementVisible selector="{{AdminCustomerGridMainActionsSection.setActive}}" stepKey="waitForDropDownOpen"/> + <click selector="{{AdminCustomerGridMainActionsSection.setActive}}" stepKey="setActive"/> + <waitForPageLoad stepKey="waitForLoad"/> + <see userInput="A total of 1 record(s) were updated." stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml index ddeefeb3c3742..f0bf7f4f1f4a7 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml @@ -25,4 +25,12 @@ <waitForPageLoad stepKey="wait"/> <scrollToTopOfPage stepKey="scrollToTop"/> </actionGroup> + <actionGroup name="adminSetCustomerInactive"> + <click selector="{{AdminCustomerAccountInformationSection.accountInformationTab}}" stepKey="goToAccountInformation"/> + <waitForElementVisible selector="{{AdminCustomerAccountInformationSection.statusInactive}}" stepKey="waitForElement"/> + <click selector="{{AdminCustomerAccountInformationSection.statusInactive}}" stepKey="clickInactive"/> + <click selector="{{AdminCustomerMainActionsSection.saveAndContinue}}" stepKey="saveAndContinue"/> + <waitForPageLoad stepKey="waitForSaving"/> + <see userInput="You saved the customer." stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml index d644b581088bc..9148e21f174ed 100755 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml @@ -15,5 +15,6 @@ <element name="actions" type="text" selector=".action-select"/> <element name="customerCheckbox" type="button" selector="//*[contains(text(),'{{arg}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']//input" parameterized="true"/> <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> + <element name="setActive" type="button" selector="//*[contains(@class, 'admin__data-grid-header')]//span[contains(@class,'action-menu-item') and text()='Set Active']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml index 91363c614c1f8..b557671342a96 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml @@ -17,4 +17,7 @@ <element name="customerEditLinkByEmail" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//a[@class='action-menu-item']" parameterized="true" timeout="30"/> <element name="customerGroupByEmail" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[text()='{{customerGroup}}']" parameterized="true"/> </section> + <section name="AdminCustomerGridInformationSection"> + <element name="status" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[contains(text(), '{{status}}')]" parameterized="true" timeout="30"/> + </section> </sections> From c240fda0d5e7dd2f6ed6ef2f8ca8bce27319c87a Mon Sep 17 00:00:00 2001 From: Aliaksei Yakimovich2 <aliaksei_yakimovich2@epam.com> Date: Thu, 13 Jun 2019 14:41:53 +0300 Subject: [PATCH 049/841] MAGETWO-70803: [GITHUB] Inconsistent CSV file Import error: #7495 - Fixed an issue with dublicated file validation; --- app/code/Magento/CatalogImportExport/Model/Import/Product.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 0b7fbaf86826b..a8395aac0f4eb 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1508,6 +1508,7 @@ public function getImagesFromRow(array $rowData) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @throws LocalizedException + * phpcs:disable Generic.Metrics.NestingLevel */ protected function _saveProducts() { @@ -1882,6 +1883,7 @@ protected function _saveProducts() return $this; } + //phpcs:enable Generic.Metrics.NestingLevel /** * Prepare array with image states (visible or hidden from product page) @@ -2726,8 +2728,6 @@ protected function _saveValidatedBunches() try { $rowData = $source->current(); } catch (\InvalidArgumentException $e) { - $this->addRowError($e->getMessage(), $this->_processedRowsCount); - $this->_processedRowsCount++; $source->next(); continue; } From 1ed2728a4566dc6cda4ab7d3e99e7eeda34844df Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Thu, 13 Jun 2019 18:50:33 +0300 Subject: [PATCH 050/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix CR comment --- .../ResourceModel/Product/Collection.php | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 6e790abd5372a..76152250c41df 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -23,7 +23,7 @@ use Magento\Framework\Indexer\DimensionFactory; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\Store; -use Magento\Catalog\Api\CategoryRepositoryInterface; +use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; /** * Product collection @@ -305,9 +305,9 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac private $urlFinder; /** - * @var CategoryRepositoryInterface + * @var CollectionFactory */ - private $categoryRepository; + private $categoryCollectionFactory; /** * Collection constructor @@ -337,7 +337,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param TableMaintainer|null $tableMaintainer * @param PriceTableResolver|null $priceTableResolver * @param DimensionFactory|null $dimensionFactory - * @param CategoryRepositoryInterface|null $categoryRepository + * @param CollectionFactory|null $categoryCollectionFactory * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -367,7 +367,7 @@ public function __construct( TableMaintainer $tableMaintainer = null, PriceTableResolver $priceTableResolver = null, DimensionFactory $dimensionFactory = null, - CategoryRepositoryInterface $categoryRepository = null + CollectionFactory $categoryCollectionFactory = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -401,8 +401,8 @@ public function __construct( $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class); $this->dimensionFactory = $dimensionFactory ?: ObjectManager::getInstance()->get(DimensionFactory::class); - $this->categoryRepository = $categoryRepository ?: ObjectManager::getInstance() - ->get(CategoryRepositoryInterface::class); + $this->categoryCollectionFactory = $categoryCollectionFactory ?: ObjectManager::getInstance() + ->get(CollectionFactory::class); } /** @@ -2105,7 +2105,11 @@ protected function _applyZeroStoreProductLimitations() */ private function getChildrenCategories(int $categoryId, array $categories): array { - $category = $this->categoryRepository->get($categoryId); + /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */ + $categoryCollection = $this->categoryCollectionFactory->create(); + $category = $categoryCollection + ->addAttributeToSelect('is_anchor')->addIdFilter([$categoryId]) + ->load()->getFirstItem(); $categories[] = $category->getId(); if ($category->getIsAnchor()) { $categoryChildren = $category->getChildren(); From 814d849b006b0f94890b4a998820087f70b5e824 Mon Sep 17 00:00:00 2001 From: Aliaksei Yakimovich2 <aliaksei_yakimovich2@epam.com> Date: Mon, 17 Jun 2019 12:44:05 +0300 Subject: [PATCH 051/841] MAGETWO-70885: [SOAP] The 'incrementId' property of the order with state 'complete' is increased after order status update - Fixed an issue with increment id change when save order via webapi; --- .../Magento/Sales/Model/OrderRepository.php | 19 +++++++++++++++++-- .../Test/Unit/Model/OrderRepositoryTest.php | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index 79548cb190754..229ea42cadc81 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -247,8 +247,11 @@ public function deleteById($id) /** * Perform persist operations for one entity * - * @param \Magento\Sales\Api\Data\OrderInterface $entity - * @return \Magento\Sales\Api\Data\OrderInterface + * @param OrderInterface $entity + * @return OrderInterface + * @throws InputException + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\AlreadyExistsException */ public function save(\Magento\Sales\Api\Data\OrderInterface $entity) { @@ -262,6 +265,18 @@ public function save(\Magento\Sales\Api\Data\OrderInterface $entity) $entity->setShippingMethod($shipping->getMethod()); } } + + $entityId = $entity->getEntityId(); + if ($entityId && $entity->getIncrementId() == null) { + try { + $loadedEntity = $this->get($entityId); + $entity->setIncrementId($loadedEntity->getIncrementId()); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock + } catch (NoSuchEntityException $e) { + // non-existent entity + } + } + $this->metadata->getMapper()->save($entity); $this->registry[$entity->getEntityId()] = $entity; return $this->registry[$entity->getEntityId()]; diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php index 7f0c0639d21f5..b9c3cd1771ca1 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php @@ -176,6 +176,7 @@ public function testSave() $shippingMock->expects($this->once())->method('getAddress'); $shippingMock->expects($this->once())->method('getMethod'); $this->metadata->expects($this->once())->method('getMapper')->willReturn($mapperMock); + $orderEntity->expects($this->once())->method('getIncrementId')->willReturn('0000000001'); $mapperMock->expects($this->once())->method('save'); $orderEntity->expects($this->any())->method('getEntityId')->willReturn(1); $this->orderRepository->save($orderEntity); From 6acb7501776a429d2767b2519ef049a649f7dd27 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Mon, 17 Jun 2019 16:38:20 +0400 Subject: [PATCH 052/841] MC-17007: "Set Active" action on customers grid does not work properly - Updated automated test script. --- .../Customer/Test/Mftf/Section/AdminCustomerGridSection.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml index b557671342a96..88d8933f4f581 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml @@ -19,5 +19,6 @@ </section> <section name="AdminCustomerGridInformationSection"> <element name="status" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[contains(text(), '{{status}}')]" parameterized="true" timeout="30"/> + <element name="associatedCompany" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[contains(text(), '{{companyName}}')]" parameterized="true" timeout="30/"/> </section> </sections> From f66a7132696966415fd6489ec48e8f8f095cdde8 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Tue, 18 Jun 2019 12:37:47 +0300 Subject: [PATCH 053/841] MC-16375: Unable to pay with Braintree Paypal (Payment Action = Authorize and Capture) for Gift Card as Guest - Fix braintree paypal quote address --- .../Plugin/DisableQuoteAddressValidation.php | 59 +++++++++++++++++++ app/code/Magento/Braintree/etc/di.xml | 1 + .../js/view/payment/method-renderer/paypal.js | 22 +++---- 3 files changed, 72 insertions(+), 10 deletions(-) create mode 100644 app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php diff --git a/app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php b/app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php new file mode 100644 index 0000000000000..c4e11a9eedb37 --- /dev/null +++ b/app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Braintree\Plugin; + +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Quote\Api\CartManagementInterface; + +/** + * Plugin for CartManagementInterface to disable quote address validation + */ +class DisableQuoteAddressValidation +{ + /** + * @var CartRepositoryInterface + */ + private $quoteRepository; + + /** + * @param CartRepositoryInterface $quoteRepository + */ + public function __construct( + CartRepositoryInterface $quoteRepository + ) { + $this->quoteRepository = $quoteRepository; + } + + /** + * Disable quote address validation before place order + * + * @param CartManagementInterface $subject + * @param \Closure $proceed + * @param int $cartId + * @param PaymentInterface|null $payment + * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function aroundPlaceOrder( + CartManagementInterface $subject, + \Closure $proceed, + int $cartId, + PaymentInterface $payment = null + ) { + $quote = $this->quoteRepository->get($cartId); + if ($quote->getPayment()->getMethod() == 'braintree_paypal' && + $quote->getCheckoutMethod() == CartManagementInterface::METHOD_GUEST) { + $billingAddress = $quote->getBillingAddress(); + $billingAddress->setShouldIgnoreValidation(true); + $quote->setBillingAddress($billingAddress); + } + return $proceed($cartId, $payment); + } +} diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index 6f8b7d1d6c368..ebb85767dec91 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -626,5 +626,6 @@ <type name="Magento\Quote\Api\CartManagementInterface"> <plugin name="order_cancellation" type="Magento\Braintree\Plugin\OrderCancellation"/> + <plugin name="disable_quote_address_validation" type="Magento\Braintree\Plugin\DisableQuoteAddressValidation"/> </type> </config> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index c46e65ffb8abd..620e2fcd00f58 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -187,17 +187,17 @@ define([ */ setBillingAddress: function (customer, address) { var billingAddress = { - street: [address.streetAddress], - city: address.locality, + street: [address.line1], + city: address.city, postcode: address.postalCode, - countryId: address.countryCodeAlpha2, + countryId: address.countryCode, email: customer.email, firstname: customer.firstName, lastname: customer.lastName, - telephone: customer.phone + telephone: customer.phone, + regionCode: address.state }; - - billingAddress['region_code'] = address.region; + billingAddress = createBillingAddress(billingAddress); quote.billingAddress(billingAddress); }, @@ -209,10 +209,12 @@ define([ beforePlaceOrder: function (payload) { this.setPaymentPayload(payload); - if ((this.isRequiredBillingAddress() || quote.billingAddress() === null) && - typeof payload.details.billingAddress !== 'undefined' - ) { - this.setBillingAddress(payload.details, payload.details.billingAddress); + if (this.isRequiredBillingAddress() || quote.billingAddress() === null) { + if (typeof payload.details.billingAddress !== 'undefined') { + this.setBillingAddress(payload.details, payload.details.billingAddress); + } else { + this.setBillingAddress(payload.details, payload.details.shippingAddress); + } } if (this.isSkipOrderReview()) { From 9c9a3fd069493c9e88406a4b6ee79d41835c8d2a Mon Sep 17 00:00:00 2001 From: Diego Cabrejas <diego@wearejh.com> Date: Tue, 18 Jun 2019 13:16:32 +0100 Subject: [PATCH 054/841] Use class name as type hint instead of self. Self breaks code generator when a preference is defined. Fixes #22769 --- .../Catalog/Model/Indexer/Category/Product/Action/Full.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eb59acb56c356..eee347c36910d 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 @@ -152,7 +152,7 @@ private function switchTables(): void * * @return $this */ - public function execute(): self + public function execute(): Full { $this->createTables(); $this->clearReplicaTables(); From 01f1f157dc2f17a0bbefcfe9ba717c96d788fb53 Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Tue, 18 Jun 2019 16:17:52 +0100 Subject: [PATCH 055/841] Reworked query in getAttributeRawValue so that it returns a store specific value even if there is no default value --- .../Model/ResourceModel/AbstractResource.php | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php index 3d7f863b7c0d3..0cd38b72b3da9 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -560,15 +560,19 @@ public function getAttributeRawValue($entityId, $attribute, $store) $store = (int) $store; if ($typedAttributes) { foreach ($typedAttributes as $table => $_attributes) { + $defaultJoinCondition = [ + $connection->quoteInto('default_value.attribute_id IN (?)', array_keys($_attributes)), + "default_value.{$this->getLinkField()} = e.{$this->getLinkField()}", + 'default_value.store_id = 0', + ]; + $select = $connection->select() - ->from(['default_value' => $table], ['attribute_id']) - ->join( - ['e' => $this->getTable($this->getEntityTable())], - 'e.' . $this->getLinkField() . ' = ' . 'default_value.' . $this->getLinkField(), - '' - )->where('default_value.attribute_id IN (?)', array_keys($_attributes)) - ->where("e.entity_id = :entity_id") - ->where('default_value.store_id = ?', 0); + ->from(['e' => $this->getTable($this->getEntityTable())], []) + ->joinLeft( + ['default_value' => $table], + implode(' AND ', $defaultJoinCondition), + [] + )->where("e.entity_id = :entity_id"); $bind = ['entity_id' => $entityId]; @@ -578,6 +582,11 @@ public function getAttributeRawValue($entityId, $attribute, $store) 'default_value.value', 'store_value.value' ); + $attributeIdExpr = $connection->getCheckSql( + 'store_value.attribute_id IS NULL', + 'default_value.attribute_id', + 'store_value.attribute_id' + ); $joinCondition = [ $connection->quoteInto('store_value.attribute_id IN (?)', array_keys($_attributes)), "store_value.{$this->getLinkField()} = e.{$this->getLinkField()}", @@ -587,18 +596,23 @@ public function getAttributeRawValue($entityId, $attribute, $store) $select->joinLeft( ['store_value' => $table], implode(' AND ', $joinCondition), - ['attr_value' => $valueExpr] + ['attribute_id' => $attributeIdExpr, 'attr_value' => $valueExpr] ); $bind['store_id'] = $store; } else { - $select->columns(['attr_value' => 'value'], 'default_value'); + $select->columns( + ['attribute_id' => 'attribute_id', 'attr_value' => 'value'], + 'default_value' + ); } $result = $connection->fetchPairs($select, $bind); foreach ($result as $attrId => $value) { - $attrCode = $typedAttributes[$table][$attrId]; - $attributesData[$attrCode] = $value; + if ($attrId !== '') { + $attrCode = $typedAttributes[$table][$attrId]; + $attributesData[$attrCode] = $value; + } } } } From 3d556b2d38522d8f71aca0bba82333a9d2d179eb Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Wed, 19 Jun 2019 10:05:42 +0400 Subject: [PATCH 056/841] MAGETWO-44170: Not pass function test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Added automated test script. --- ...AdminProductTypeSwitchingOnEditingTest.xml | 423 ++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml new file mode 100644 index 0000000000000..116fbc1b6b455 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -0,0 +1,423 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Simple product type switching on editing to configurable product"/> + <description value="Simple product type switching on editing to configurable product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <comment userInput="Create product" stepKey="commentCreateProduct"/> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <!--Create attribute with options--> + <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Add configurations to product--> + <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveConfigProductForm"/> + <!--Assert configurable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <!--Assert configurable product on storefront--> + <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage"/> + <waitForPageLoad stepKey="homeWaitForPageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName1"> + <argument name="phrase" value="$$createProduct.name$$"/> + </actionGroup> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeConfProductInSearchResultPage"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfProductChildOneInSearchResultPage"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfProductChildTwoInSearchResultPage"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> + <see userInput="option1" stepKey="verifyOption1Exists"/> + <see userInput="option2" stepKey="verifyOption2Exists"/> + </test> + <test name="AdminConfigurableProductTypeSwitchingToVirtualProductTest" extends="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Configurable product type switching on editing to virtual product"/> + <description value="Configurable product type switching on editing to virtual product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <!--Delete product configurations--> + <comment userInput="Delete product configuration" stepKey="commentDeleteConfigs"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToConfigProductPage"/> + <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> + <conditionalClick selector="{{ AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection" /> + <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption1Actions"/> + <click selector="{{AdminProductFormConfigurationsSection.removeProductBtn}}" stepKey="clickRemoveOption1"/> + <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption2Actions"/> + <click selector="{{AdminProductFormConfigurationsSection.removeProductBtn}}" stepKey="clickRemoveOption2"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{SimpleProduct2.price}}" stepKey="fillProductPrice"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{SimpleProduct2.quantity}}" stepKey="fillProductQty"/> + <clearField selector="{{AdminProductFormSection.productWeight}}" stepKey="clearWeightField"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has no weight" stepKey="selectNoWeight"/> + <actionGroup ref="saveProductForm" stepKey="saveVirtualProductForm"/> + <!--Assert virtual product on Admin product page grid--> + <comment userInput="Assert virtual product on Admin product page grid" stepKey="commentAssertVirtualProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForVirtual"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuForVirtual"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeVirtualProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Virtual Product" stepKey="seeVirtualProductTypeInGrid"/> + <!--Assert virtual product on storefront--> + <comment userInput="Assert virtual product on storefront" stepKey="commentAssertVirtualProductOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="openStorefrontPage"/> + <waitForPageLoad stepKey="storefrontWaitForPageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByVirtualProductName"> + <argument name="phrase" value="$$createProduct.name$$"/> + </actionGroup> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeVirtualProductInSearchResultPage"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openVirtualProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontVirtualProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertVirtualProductInStock"/> + </test> + <test name="AdminVirtualProductTypeSwitchingToConfigurableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Virtual product type switching on editing to configurable product"/> + <description value="Virtual product type switching on editing to configurable product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <comment userInput="Create product" stepKey="commentCreateProduct"/> + <createData entity="VirtualProduct" stepKey="createProduct"/> + <!--Create attribute with options--> + <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Add configurations to product--> + <comment userInput="Add configurations to product" stepKey="commentAddConfigurations"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToConfigProductPage"/> + <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForConfigurableProduct"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurationsForProduct"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveNewConfigurableProductForm"/> + <!--Assert configurable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigurableProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfigurable"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuForConfigurable"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeConfigurableProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeConfigurableProductTypeInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfigurableProductSkuInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfigurableProductSkuInGrid2"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearConfigurableProductFilters"/> + <!--Assert configurable product on storefront--> + <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigurableProductOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontHomePage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByConfigurableProductName"> + <argument name="phrase" value="$$createProduct.name$$"/> + </actionGroup> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeConfigurableProductInSearchResultPage"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfigurableProductChildOneInSearchResultPage"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfigurableProductChildTwoInSearchResultPage"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openConfigurableProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontConfigurableProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertConfigurableProductInStock"/> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickConfigurableAttributeDropDown"/> + <see userInput="option1" stepKey="verifyConfigurableProductOption1Exists"/> + <see userInput="option2" stepKey="verifyConfigurableProductOption2Exists"/> + </test> + <test name="AdminVirtualProductTypeSwitchingToDownloadableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Virtual product type switching on editing to Downloadable product"/> + <description value="Virtual product type switching on editing to Downloadable product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <comment userInput="Create product" stepKey="commentCreateProduct"/> + <createData entity="VirtualProduct" stepKey="createProduct"/> + </before> + <after> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Change product type to Downloadable--> + <comment userInput="-Change product type to Downloadable" stepKey="commentCreateDownloadable"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForDownloadableProductPageLoad"/> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveDownloadableProductForm"/> + <!--Assert downloadable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertDownloadableProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Downloadable Product" stepKey="seeDownloadableProductTypeInGrid"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearDownloadableProductFilters"/> + <!--Assert downloadable product on storefront--> + <comment userInput="Assert downloadable product on storefront" stepKey="commentAssertDownloadableProductOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontHomePage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByDownloadableProductName"> + <argument name="phrase" value="$$createProduct.name$$"/> + </actionGroup> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductInSearchResultPage"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontDownloadableProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertDownloadableProductInStock"/> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}" stepKey="seeDownloadableLink" /> + </test> + <test name="AdminDownloadableProductTypeSwitchingToSimpleProductTest" extends="AdminVirtualProductTypeSwitchingToDownloadableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Downloadable product type switching on editing to Simple product"/> + <description value="Downloadable product type switching on editing to Simple product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <!--Change product type to Simple--> + <comment userInput="Change product type to Simple Product" stepKey="commentCreateSimple"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForProduct"/> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + <!--Assert simple product on Admin product page grid--> + <comment userInput="Assert simple product in Admin product page grid" stepKey="commentAssertProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogSimpleProductPage"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterSimpleProductGridBySku"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeSimpleProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Simple Product" stepKey="seeSimpleProductTypeInGrid"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearSimpleProductFilters"/> + <!--Assert simple product on storefront--> + <comment userInput="Assert simple product on storefront" stepKey="commentAssertSimpleProductOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchBySimpleProductName"> + <argument name="phrase" value="$$createProduct.name$$"/> + </actionGroup> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeSimpleProductInSearchResultPage"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openSimpleProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontSimpleProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertSimpleProductInStock"/> + <dontSeeElement selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}" stepKey="dontSeeDownloadableLink" /> + </test> + <test name="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Simple product type switching on editing to downloadable product"/> + <description value="Simple product type switching on editing to downloadable product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <comment userInput="Create product" stepKey="commentCreateProduct"/> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + <after> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Change product type to Downloadable--> + <comment userInput="Change product type to Downloadable" stepKey="commentCreateDownloadable"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForDownloadableProductPageLoad"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has no weight" stepKey="selectNoWeightForProduct"/> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveDownloadableProductForm"/> + <!--Assert downloadable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertDownloadableProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Downloadable Product" stepKey="seeDownloadableProductTypeInGrid"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearDownloadableProductFilters"/> + <!--Assert downloadable product on storefront--> + <comment userInput="Assert downloadable product on storefront" stepKey="commentAssertDownloadableProductOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontHomePage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByDownloadableProductName"> + <argument name="phrase" value="$$createProduct.name$$"/> + </actionGroup> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductInSearchResultPage"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontDownloadableProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertDownloadableProductInStock"/> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}" stepKey="seeDownloadableLink" /> + </test> + <test name="AdminDownloadableProductTypeSwitchingToConfigurableProductTest" extends="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Downloadable product type switching on editing to configurable product"/> + <description value="Downloadable product type switching on editing to configurable product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <!--Create attribute with options--> + <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Add configurations to product--> + <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection"/> + <unCheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForProduct"/> + <actionGroup ref="saveProductForm" stepKey="saveDownloadssdableProductForm"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveConfigProductForm"/> + <!--Assert configurable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfig"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuFroConfig"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <!--Assert configurable product on storefront--> + <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage"/> + <waitForPageLoad stepKey="homeWaitForPageLoad"/> + <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName1"> + <argument name="phrase" value="$$createProduct.name$$"/> + </actionGroup> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeConfProductInSearchResultPage"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfProductChildOneInSearchResultPage"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfProductChildTwoInSearchResultPage"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> + <see userInput="option1" stepKey="verifyOption1Exists"/> + <see userInput="option2" stepKey="verifyOption2Exists"/> + </test> +</tests> From e135272787c20f1f2a5f63c9b04a1da634ee8d2a Mon Sep 17 00:00:00 2001 From: Ani Tumanyan <ani_tumanyan@epam.com> Date: Wed, 19 Jun 2019 10:19:23 +0400 Subject: [PATCH 057/841] MC-15341: Default product numbers to display results in poor display on Desktop - Added automated Test script --- ...heckDefaultNumberProductsToDisplayTest.xml | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml new file mode 100644 index 0000000000000..6fb4f816d0b9e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml @@ -0,0 +1,198 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CheckDefaultNumbersProductsToDisplayTest"> + <annotations> + <features value="Catalog"/> + <title value="Check default numbers: products to display"/> + <description value="Check default numbers: products to display"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17386"/> + <useCaseId value="MC-15341"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Login as Admin --> + <comment userInput="Login as Admin" stepKey="commentLoginAsAdmin"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <!-- Set default configurations --> + <comment userInput="Set default configurations" stepKey="commentSetDefaultCategory"/> + <magentoCLI command="config:set catalog/frontend/grid_per_page_values 12,24,36" stepKey="setAllowedProductsPerPageValue"/> + <magentoCLI command="config:set catalog/frontend/grid_per_page 12" stepKey="setDefaultProductsPerPageValue"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Create 37 Products and Subcategory --> + <comment userInput="Create 37 Products and Subcategory" stepKey="commentCreateData"/> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProductOne"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwo"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThree"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductFour"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductFive"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductSix"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductSeven"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductEight"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductNine"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductEleven"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwelve"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirteen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductFourteen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductFifteen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductSixteen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductSeventeen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductEighteen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductNineteen"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwenty"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentyOne"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentyTwo"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentyThree"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentyFour"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentyFive"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentySix"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentySeven"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentyEight"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductTwentyNine"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirty"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirtyOne"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirtyTwo"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirtyThree"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirtyFour"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirtyFive"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirtySix"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProductThirtySeven"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProductOne" stepKey="deleteProductOne"/> + <deleteData createDataKey="createSimpleProductTwo" stepKey="deleteProductTwo"/> + <deleteData createDataKey="createSimpleProductThree" stepKey="deleteProductThree"/> + <deleteData createDataKey="createSimpleProductFour" stepKey="deleteProductFour"/> + <deleteData createDataKey="createSimpleProductFive" stepKey="deleteProductFive"/> + <deleteData createDataKey="createSimpleProductSix" stepKey="deleteProductSix"/> + <deleteData createDataKey="createSimpleProductSeven" stepKey="deleteProductSeven"/> + <deleteData createDataKey="createSimpleProductEight" stepKey="deleteProductEight"/> + <deleteData createDataKey="createSimpleProductNine" stepKey="deleteProductNine"/> + <deleteData createDataKey="createSimpleProductTen" stepKey="deleteProductTen"/> + <deleteData createDataKey="createSimpleProductEleven" stepKey="deleteProductEleven"/> + <deleteData createDataKey="createSimpleProductTwelve" stepKey="deleteProductTwelve"/> + <deleteData createDataKey="createSimpleProductThirteen" stepKey="deleteProductThirteen"/> + <deleteData createDataKey="createSimpleProductFourteen" stepKey="deleteProductFourteen"/> + <deleteData createDataKey="createSimpleProductFifteen" stepKey="deleteProductFifteen"/> + <deleteData createDataKey="createSimpleProductSixteen" stepKey="deleteProductSixteen"/> + <deleteData createDataKey="createSimpleProductSeventeen" stepKey="deleteProductSeventeen"/> + <deleteData createDataKey="createSimpleProductEighteen" stepKey="deleteProductEighteen"/> + <deleteData createDataKey="createSimpleProductNineteen" stepKey="deleteProductNineteen"/> + <deleteData createDataKey="createSimpleProductTwenty" stepKey="deleteProductTwenty"/> + <deleteData createDataKey="createSimpleProductTwentyOne" stepKey="deleteProductTwentyOne"/> + <deleteData createDataKey="createSimpleProductTwentyTwo" stepKey="deleteProductTwentyTwo"/> + <deleteData createDataKey="createSimpleProductTwentyThree" stepKey="deleteProductTwentyThree"/> + <deleteData createDataKey="createSimpleProductTwentyFour" stepKey="deleteProductTwentyFour"/> + <deleteData createDataKey="createSimpleProductTwentyFive" stepKey="deleteProductTwentyFive"/> + <deleteData createDataKey="createSimpleProductTwentySix" stepKey="deleteProductTwentySix"/> + <deleteData createDataKey="createSimpleProductTwentySeven" stepKey="deleteProductTwentySeven"/> + <deleteData createDataKey="createSimpleProductTwentyEight" stepKey="deleteProductTwentyEight"/> + <deleteData createDataKey="createSimpleProductTwentyNine" stepKey="deleteProductTwentyNine"/> + <deleteData createDataKey="createSimpleProductThirty" stepKey="deleteProductThirty"/> + <deleteData createDataKey="createSimpleProductThirtyOne" stepKey="deleteProductThirtyOne"/> + <deleteData createDataKey="createSimpleProductThirtyTwo" stepKey="deleteProductThirtyTwo"/> + <deleteData createDataKey="createSimpleProductThirtyThree" stepKey="deleteProductThirtyThree"/> + <deleteData createDataKey="createSimpleProductThirtyFour" stepKey="deleteProductThirtyFour"/> + <deleteData createDataKey="createSimpleProductThirtyFive" stepKey="deleteProductThirtyFive"/> + <deleteData createDataKey="createSimpleProductThirtySix" stepKey="deleteProductThirtySix"/> + <deleteData createDataKey="createSimpleProductThirtySeven" stepKey="deleteProductThirtySeven"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Open storefront on the category page --> + <comment userInput="Open storefront on the category page" stepKey="commentOpenStorefront"/> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="goToStorefrontCreatedCategoryPage"/> + <!-- Check the drop-down at the bottom of page contains options --> + <comment userInput="Check the drop-down at the bottom of page contains options" stepKey="commentCheckOptions"/> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToBottomToolbarSection"/> + <assertElementContainsAttribute selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" attribute="value" expectedValue="12" stepKey="assertPerPageFirstValue" /> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="24" stepKey="selectPerPageSecondValue" /> + <assertElementContainsAttribute selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" attribute="value" expectedValue="24" stepKey="assertPerPageSecondValue" /> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="36" stepKey="selectPerPageThirdValue" /> + <assertElementContainsAttribute selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" attribute="value" expectedValue="36" stepKey="assertPerPageThirdValue" /> + </test> +</tests> From d8486ba3ef50a8f111654d4e1fa5b7a49cd6ade2 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <svizev.igor@gmail.com> Date: Wed, 19 Jun 2019 16:47:00 +0300 Subject: [PATCH 058/841] Fix unexpected diff between builds in generated/metadata directory Sort all path definitions before generating config --- .../Setup/Module/Di/App/Task/Operation/Area.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/Area.php b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/Area.php index edc2a485278a6..61eae4b2ffff6 100644 --- a/setup/src/Magento/Setup/Module/Di/App/Task/Operation/Area.php +++ b/setup/src/Magento/Setup/Module/Di/App/Task/Operation/Area.php @@ -88,6 +88,8 @@ public function doOperation() } } + $this->sortDefinitions($definitionsCollection); + $areaCodes = array_merge([App\Area::AREA_GLOBAL], $this->areaList->getCodes()); foreach ($areaCodes as $areaCode) { $config = $this->configReader->generateCachePerScope($definitionsCollection, $areaCode); @@ -124,4 +126,18 @@ public function getName() { return 'Area configuration aggregation'; } + + /** + * Sort definitions to make reproducible result + * + * @param DefinitionsCollection $definitionsCollection + */ + private function sortDefinitions(DefinitionsCollection $definitionsCollection): void + { + $definitions = $definitionsCollection->getCollection(); + + ksort($definitions); + + $definitionsCollection->initialize($definitions); + } } From 3fd8742fd0d14531e1d7b7a90b8c8deb0b972f36 Mon Sep 17 00:00:00 2001 From: Evgeny Petrov <evgeny_petrov@epam.com> Date: Thu, 20 Jun 2019 12:41:31 +0300 Subject: [PATCH 059/841] MC-15341: Default product numbers to display results in poor display on Desktop - Fixes of failed tests --- .../AdminCheckPaginationInStorefrontTest.xml | 62 +++++++++++++------ .../StorefrontOrderPagerDisplayedTest.xml | 2 +- .../Test/StorefrontOrderPagerIsAbsentTest.xml | 2 +- 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml index f40a62c164ecc..384d8ddea4f17 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml @@ -41,6 +41,16 @@ <createData entity="PaginationProduct" stepKey="simpleProduct18"/> <createData entity="PaginationProduct" stepKey="simpleProduct19"/> <createData entity="PaginationProduct" stepKey="simpleProduct20"/> + <createData entity="PaginationProduct" stepKey="simpleProduct21"/> + <createData entity="PaginationProduct" stepKey="simpleProduct22"/> + <createData entity="PaginationProduct" stepKey="simpleProduct23"/> + <createData entity="PaginationProduct" stepKey="simpleProduct24"/> + <createData entity="PaginationProduct" stepKey="simpleProduct25"/> + <createData entity="PaginationProduct" stepKey="simpleProduct26"/> + <createData entity="PaginationProduct" stepKey="simpleProduct27"/> + <createData entity="PaginationProduct" stepKey="simpleProduct28"/> + <createData entity="PaginationProduct" stepKey="simpleProduct29"/> + <createData entity="PaginationProduct" stepKey="simpleProduct30"/> <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> </before> <after> @@ -67,6 +77,16 @@ <deleteData createDataKey="simpleProduct18" stepKey="deleteSimpleProduct18"/> <deleteData createDataKey="simpleProduct19" stepKey="deleteSimpleProduct19"/> <deleteData createDataKey="simpleProduct20" stepKey="deleteSimpleProduct20"/> + <deleteData createDataKey="simpleProduct21" stepKey="deleteSimpleProduct21"/> + <deleteData createDataKey="simpleProduct22" stepKey="deleteSimpleProduct22"/> + <deleteData createDataKey="simpleProduct23" stepKey="deleteSimpleProduct23"/> + <deleteData createDataKey="simpleProduct24" stepKey="deleteSimpleProduct24"/> + <deleteData createDataKey="simpleProduct25" stepKey="deleteSimpleProduct25"/> + <deleteData createDataKey="simpleProduct26" stepKey="deleteSimpleProduct26"/> + <deleteData createDataKey="simpleProduct27" stepKey="deleteSimpleProduct27"/> + <deleteData createDataKey="simpleProduct28" stepKey="deleteSimpleProduct28"/> + <deleteData createDataKey="simpleProduct29" stepKey="deleteSimpleProduct29"/> + <deleteData createDataKey="simpleProduct30" stepKey="deleteSimpleProduct30"/> <actionGroup ref="logout" stepKey="logout"/> </after> @@ -86,11 +106,13 @@ <waitForElementVisible selector="{{CatalogProductsSection.resetFilter}}" time="30" stepKey="waitForResetButtonToVisible"/> <click selector="{{CatalogProductsSection.resetFilter}}" stepKey="clickOnResetFilter"/> <waitForPageLoad stepKey="waitForPageToLoad3"/> - <selectOption selector="{{AdminProductGridFilterSection.productPerPage}}" userInput="20" stepKey="selectPagePerView"/> + + <selectOption selector="{{AdminProductGridFilterSection.productPerPage}}" userInput="30" stepKey="selectPagePerView"/> + <wait stepKey="waitFroPageToLoad1" time="30"/> <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="pagi" stepKey="selectProduct1"/> <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> - <waitForPageLoad stepKey="waitFroPageToLoad1"/> - <see selector="{{AdminProductGridFilterSection.productCount}}" userInput="20" stepKey="seeNumberOfProductsFound"/> + <waitForPageLoad stepKey="waitFroPageToLoad2"/> + <see selector="{{AdminProductGridFilterSection.productCount}}" userInput="30" stepKey="seeNumberOfProductsFound"/> <click selector="{{AdminCategoryProductsGridSection.productSelectAll}}" stepKey="selectSelectAll"/> <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> <waitForPageLoad stepKey="waitForCategorySaved"/> @@ -104,73 +126,73 @@ <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="selectCategory"/> <waitForPageLoad stepKey="waitForProductToLoad"/> - <!--Select 9 items per page and verify number of products displayed in each page --> + <!--Select 12 items per page and verify number of products displayed in each page --> <conditionalClick selector="{{StorefrontCategoryTopToolbarSection.gridMode}}" visible="true" dependentSelector="{{StorefrontCategoryTopToolbarSection.gridMode}}" stepKey="seeProductGridIsActive"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToBottomToolbarSection"/> - <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="9" stepKey="selectPerPageOption"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="12" stepKey="selectPerPageOption"/> <!--Verify number of products displayed in First Page --> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInFirstPage"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInFirstPage"/> <!--Verify number of products displayed in Second Page --> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton"/> <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage"/> <waitForPageLoad stepKey="waitForPageToLoad4"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInSecondPage"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInSecondPage"/> <!--Verify number of products displayed in third Page --> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton1"/> <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage1"/> <waitForPageLoad stepKey="waitForPageToLoad2"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="2" stepKey="seeNumberOfProductsInThirdPage"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInThirdPage"/> <!--Change Pages using Previous Page selector and verify number of products displayed in each page--> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage"/> <click selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="clickOnPreviousPage1"/> <waitForPageLoad stepKey="waitForPageToLoad5"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInSecondPage1"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInSecondPage1"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage1"/> <click selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="clickOnPreviousPage2"/> <waitForPageLoad stepKey="waitForPageToLoad6"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInFirstPage1"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInFirstPage1"/> <!--Select Pages by using page Number and verify number of products displayed--> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToPreviousPage2"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('2')}}" stepKey="clickOnPage2"/> <waitForPageLoad stepKey="waitForPageToLoad7"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInSecondPage2"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInSecondPage2"/> <!--Select Third Page using page number--> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToPreviousPage3"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('3')}}" stepKey="clickOnThirdPage"/> <waitForPageLoad stepKey="waitForPageToLoad8"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="2" stepKey="seeNumberOfProductsInThirdPage2"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInThirdPage2"/> <!--Select First Page using page number--> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage4"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage"/> <waitForPageLoad stepKey="waitForPageToLoad9"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsFirstPage2"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsFirstPage2"/> - <!--Select 15 items per page and verify number of products displayed in each page --> + <!--Select 24 items per page and verify number of products displayed in each page --> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage"/> - <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="15" stepKey="selectPerPageOption1"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="24" stepKey="selectPerPageOption1"/> <waitForPageLoad stepKey="waitForPageToLoad10"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="15" stepKey="seeNumberOfProductsInFirstPage3"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="24" stepKey="seeNumberOfProductsInFirstPage3"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton2"/> <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage2"/> <waitForPageLoad stepKey="waitForPageToLoad11"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="5" stepKey="seeNumberOfProductsInSecondPage3"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInSecondPage3"/> <!--Select First Page using page number--> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="scrollToPreviousPage5"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage2"/> <waitForPageLoad stepKey="waitForPageToLoad13"/> - <!--Select 30 items per page and verify number of products displayed in each page --> + <!--Select 36 items per page and verify number of products displayed in each page --> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage4"/> - <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="30" stepKey="selectPerPageOption2"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="36" stepKey="selectPerPageOption2"/> <waitForPageLoad stepKey="waitForPageToLoad12"/> - <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="20" stepKey="seeNumberOfProductsInFirstPage4"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="30" stepKey="seeNumberOfProductsInFirstPage4"/> </test> </tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerDisplayedTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerDisplayedTest.xml index b8772f24a2a42..0e58bb84988a2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerDisplayedTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerDisplayedTest.xml @@ -129,7 +129,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> <scrollTo selector="{{StorefrontCategoryMainSection.perPage}}" stepKey="scrollToLimiter"/> - <selectOption userInput="30" selector="{{StorefrontCategoryMainSection.perPage}}" stepKey="selectLimitOnPage"/> + <selectOption userInput="36" selector="{{StorefrontCategoryMainSection.perPage}}" stepKey="selectLimitOnPage"/> <waitForPageLoad stepKey="waitForLoadProducts"/> <scrollToTopOfPage stepKey="scrollToTopOfPage"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerIsAbsentTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerIsAbsentTest.xml index 9909fca44fe2c..3ff8a7791d88b 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerIsAbsentTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontOrderPagerIsAbsentTest.xml @@ -125,7 +125,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> <scrollTo selector="{{StorefrontCategoryMainSection.perPage}}" stepKey="scrollToLimiter"/> - <selectOption userInput="30" selector="{{StorefrontCategoryMainSection.perPage}}" stepKey="selectLimitOnPage"/> + <selectOption userInput="36" selector="{{StorefrontCategoryMainSection.perPage}}" stepKey="selectLimitOnPage"/> <waitForPageLoad stepKey="waitForLoadProducts"/> <scrollToTopOfPage stepKey="scrollToTopOfPage"/> From fd3a8ae0e7fb437277e6e0f8fbec088582a49c2d Mon Sep 17 00:00:00 2001 From: Nikita Chubukov <nikita_chubukov@epam.com> Date: Thu, 20 Jun 2019 16:29:25 +0300 Subject: [PATCH 060/841] MC-15256: Exported customer without modification can not be imported - Changed import validation logic for attributes with empty select value --- .../Model/Import/Customer.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index ab940c9e84533..f181119f1acf5 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CustomerImportExport\Model\Import; use Magento\Customer\Api\Data\CustomerInterface; @@ -288,9 +290,12 @@ private function getCustomerEntityFieldsToUpdate(array $entitiesToUpdate): array { $firstCustomer = reset($entitiesToUpdate); $columnsToUpdate = array_keys($firstCustomer); - $customerFieldsToUpdate = array_filter($this->customerFields, function ($field) use ($columnsToUpdate) { - return in_array($field, $columnsToUpdate); - }); + $customerFieldsToUpdate = array_filter( + $this->customerFields, + function ($field) use ($columnsToUpdate) { + return in_array($field, $columnsToUpdate); + } + ); return $customerFieldsToUpdate; } @@ -606,6 +611,10 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber) } if (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) { + if ($attributeParams['type'] == 'select' && empty($rowData[$attributeCode])) { + continue; + } + $this->isAttributeValid( $attributeCode, $attributeParams, From 62bfe249b9dd0b1b6e2517152364a94b422470fc Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Fri, 21 Jun 2019 14:08:24 +0300 Subject: [PATCH 061/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix CR comments. --- .../Model/ResourceModel/Product/Collection.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 76152250c41df..7127aa2cb3e77 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -2068,8 +2068,7 @@ protected function _applyProductLimitations() protected function _applyZeroStoreProductLimitations() { $filters = $this->_productLimitationFilters; - $categories = []; - $categories = $this->getChildrenCategories((int)$filters['category_id'], $categories); + $categories = $this->getChildrenCategories((int)$filters['category_id'], []); $conditions = [ 'cat_pro.product_id=e.entity_id', @@ -2105,13 +2104,16 @@ protected function _applyZeroStoreProductLimitations() */ private function getChildrenCategories(int $categoryId, array $categories): array { + $categories[] = $categoryId; + /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */ $categoryCollection = $this->categoryCollectionFactory->create(); $category = $categoryCollection - ->addAttributeToSelect('is_anchor')->addIdFilter([$categoryId]) - ->load()->getFirstItem(); - $categories[] = $category->getId(); - if ($category->getIsAnchor()) { + ->addAttributeToSelect('is_anchor') + ->addAttributeToFilter('is_anchor', 1) + ->addIdFilter([$categoryId]) + ->getFirstItem(); + if ($category) { $categoryChildren = $category->getChildren(); $categoryChildrenIds = explode(',', $categoryChildren); foreach ($categoryChildrenIds as $categoryChildrenId) { From 9d0022d9b13c581e71f1b6c62b1de518950f256f Mon Sep 17 00:00:00 2001 From: Dzmitry Tabusheu <dzmitry_tabusheu@epam.com> Date: Fri, 21 Jun 2019 17:12:19 +0300 Subject: [PATCH 062/841] MAGETWO-45666: "Refresh cache" message is not displayed when changing category page layout - Added observer for invalidating cache on category design change --- .../InvalidateCacheOnCategoryDesignChange.php | 59 +++++++++++++++++++ ...cheAfterChangingCategoryPageLayoutTest.xml | 51 ++++++++++++++++ app/code/Magento/Catalog/etc/events.xml | 1 + 3 files changed, 111 insertions(+) create mode 100644 app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml diff --git a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php new file mode 100644 index 0000000000000..6bad77b26ad0c --- /dev/null +++ b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Observer; + +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; + +/** + * Observer for invalidating cache on catalog category design change + */ +class InvalidateCacheOnCategoryDesignChange implements ObserverInterface +{ + /** + * @var array + */ + private $designAttributes = [ + 'custom_design', + 'page_layout', + 'custom_layout_update', + 'custom_apply_to_products', + 'custom_use_parent_settings' + ]; + + /** + * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList + */ + public function __construct(\Magento\Framework\App\Cache\TypeListInterface $cacheTypeList) + { + $this->cacheTypeList = $cacheTypeList; + } + + /** + * Invalidate cache on category design attribute value changed + * + * @param \Magento\Framework\Event\Observer $observer + */ + public function execute(Observer $observer) + { + $category = $observer->getEvent()->getEntity(); + if (!$category->isObjectNew()) { + foreach ($this->designAttributes as $designAttribute) { + if ($category->dataHasChangedFor($designAttribute)) { + $this->cacheTypeList->invalidate( + [ + \Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER, + \Magento\Framework\App\Cache\Type\Layout::TYPE_IDENTIFIER + ] + ); + break; + } + } + } + } +} diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml new file mode 100644 index 0000000000000..5a94dd4f04d24 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="DisplayRefreshCacheAfterChangingCategoryPageLayoutTest"> + <annotations> + <features value="Catalog"/> + <title value="'Refresh cache' admin notification is displayed when changing category page layout"/> + <description value="'Refresh cache' message is not displayed when changing category page layout"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17031"/> + <useCaseId value="MAGETWO-45666"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Create category, flush cache and log in --> + <comment userInput="Create category, flush cache and log in" stepKey="createCategoryAndLogIn"/> + <createData entity="SimpleSubCategory" stepKey="simpleCategory"/> + <actionGroup ref="LoginAsAdmin" stepKey="logInAsAdmin"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <!-- Delete category and log out --> + <comment userInput="Delete category and log out" stepKey="deleteCategoryAndLogOut"/> + <deleteData createDataKey="simpleCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logOutFromAdmin"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + <!-- Navigate to category details page --> + <comment userInput="Navigate to category details page" stepKey="navigateToAdminCategoryPage"/> + <actionGroup ref="goToAdminCategoryPageById" stepKey="goToAdminCategoryPage"> + <argument name="id" value="$$simpleCategory.id$$"/> + </actionGroup> + <!-- Open design tab and set layout --> + <comment userInput="Open design tab and set layout" stepKey="setLayoutAndSave"/> + <click selector="{{CategoryDesignSection.DesignTab}}" stepKey="clickOnDesignTab"/> + <waitForElementVisible selector="{{CategoryDesignSection.LayoutDropdown}}" stepKey="waitForLayoutDropDown" /> + <selectOption selector="{{CategoryDesignSection.LayoutDropdown}}" userInput="2 columns with right bar" stepKey="selectAnOption" /> + <click selector="{{ContentManagementSection.Save}}" stepKey="clickSaveConfig" /> + <waitForPageLoad stepKey="waitSaveToApply"/> + <!-- See if warning message displays --> + <comment userInput="See if warning message displays" stepKey="checkWarningMessagePresence"/> + <see selector="{{AdminMessagesSection.warningMessage}}" userInput="Please go to Cache Management and refresh cache types" stepKey="seeWarningMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/etc/events.xml b/app/code/Magento/Catalog/etc/events.xml index 5bcdc88369064..f4345ce719a19 100644 --- a/app/code/Magento/Catalog/etc/events.xml +++ b/app/code/Magento/Catalog/etc/events.xml @@ -26,6 +26,7 @@ </event> <event name="magento_catalog_api_data_categoryinterface_save_after"> <observer name="legacy_category_save_after" instance="Magento\Framework\EntityManager\Observer\AfterEntitySave" /> + <observer name="invalidate_cache_on_category_design_change" instance="Magento\Catalog\Observer\InvalidateCacheOnCategoryDesignChange" /> </event> <event name="magento_catalog_api_data_categoryinterface_delete_before"> <observer name="legacy_category_delete_before" instance="Magento\Framework\EntityManager\Observer\BeforeEntityDelete" /> From d40388edad3e5c812bb455628795172f412845a3 Mon Sep 17 00:00:00 2001 From: Maksym Novik <m.novik@ism-ukraine.com> Date: Sat, 22 Jun 2019 12:11:45 +0300 Subject: [PATCH 063/841] GraphQl-220: Implement exception logging #355. Cleaned up code. --- .../Framework/GraphQl/Query/ErrorHandler.php | 38 ++++++++++--------- .../GraphQl/Query/ErrorHandlerInterface.php | 4 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php index 2f97ae238c6a7..fb5e921e72f6e 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php @@ -7,6 +7,10 @@ namespace Magento\Framework\GraphQl\Query; +use GraphQL\Error\ClientAware; +use GraphQL\Error\Error; +use Magento\Framework\Logger\Monolog; + /** * Class ErrorHandler * @@ -15,12 +19,12 @@ class ErrorHandler implements ErrorHandlerInterface { /** - * @var \Magento\Framework\Logger\Monolog + * @var Monolog */ private $clientLogger; /** - * @var \Magento\Framework\Logger\Monolog + * @var Monolog */ private $serverLogger; @@ -35,25 +39,25 @@ class ErrorHandler implements ErrorHandlerInterface private $serverErrorCategories; /** - * @var \Magento\Framework\Logger\Monolog + * @var Monolog */ private $generalLogger; /** * ErrorHandler constructor. * - * @param \Magento\Framework\Logger\Monolog $clientLogger - * @param \Magento\Framework\Logger\Monolog $serverLogger - * @param \Magento\Framework\Logger\Monolog $generalLogger - * @param array $clientErrorCategories - * @param array $serverErrorCategories + * @param Monolog $clientLogger + * @param Monolog $serverLogger + * @param Monolog $generalLogger + * @param array $clientErrorCategories + * @param array $serverErrorCategories * * @SuppressWarnings(PHPMD.LongVariable) */ public function __construct( - \Magento\Framework\Logger\Monolog $clientLogger, - \Magento\Framework\Logger\Monolog $serverLogger, - \Magento\Framework\Logger\Monolog $generalLogger, + Monolog $clientLogger, + Monolog $serverLogger, + Monolog $generalLogger, array $clientErrorCategories = [], array $serverErrorCategories = [] ) { @@ -67,15 +71,15 @@ public function __construct( /** * Handle errors * - * @param \GraphQL\Error\Error[] $errors - * @param callable $formatter + * @param Error[] $errors + * @param callable $formatter * * @return array */ - public function handle(array $errors, callable $formatter):array + public function handle(array $errors, callable $formatter): array { return array_map( - function (\GraphQL\Error\ClientAware $error) use ($formatter) { + function (ClientAware $error) use ($formatter) { $this->logError($error); return $formatter($error); @@ -85,11 +89,11 @@ function (\GraphQL\Error\ClientAware $error) use ($formatter) { } /** - * @param \GraphQL\Error\ClientAware $error + * @param ClientAware $error * * @return boolean */ - private function logError(\GraphQL\Error\ClientAware $error):bool + private function logError(ClientAware $error): bool { if (in_array($error->getCategory(), $this->clientErrorCategories)) { return $this->clientLogger->error($error); diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php index f63468fa4ddb8..dbb149a865746 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php @@ -7,6 +7,8 @@ namespace Magento\Framework\GraphQl\Query; +use GraphQL\Error\Error; + /** * Interface ErrorHandlerInterface * @@ -20,7 +22,7 @@ interface ErrorHandlerInterface /** * Handle errors * - * @param \GraphQL\Error\Error[] $errors + * @param Error[] $errors * @param callable $formatter * * @return array From 6a5c7e2f52aac7af3378270e69604ecb2da21695 Mon Sep 17 00:00:00 2001 From: Maksym Novik <m.novik@ism-ukraine.com> Date: Sat, 22 Jun 2019 13:19:54 +0300 Subject: [PATCH 064/841] GraphQl-220: Implement exception logging #355. Moved DI to graphql area; Create ResolveLogger service; Use ClientAvare->isClientSafe() to divide exceptions into client and server --- app/etc/di.xml | 57 -------------- app/etc/graphql/di.xml | 41 ++++++++++ .../Framework/GraphQl/Query/ErrorHandler.php | 77 +++---------------- .../GraphQl/Query/ErrorHandlerInterface.php | 13 ++-- .../GraphQl/Query/Resolver/ResolveLogger.php | 49 ++++++++++++ .../Query/Resolver/ResolveLoggerInterface.php | 28 +++++++ 6 files changed, 136 insertions(+), 129 deletions(-) create mode 100644 app/etc/graphql/di.xml create mode 100644 lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php create mode 100644 lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLoggerInterface.php diff --git a/app/etc/di.xml b/app/etc/di.xml index 21cdea8510638..200a56201239d 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1768,61 +1768,4 @@ <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillPutInterface" type="Magento\Framework\MessageQueue\PoisonPill\PoisonPillPut"/> <preference for="Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface" type="Magento\Framework\MessageQueue\PoisonPill\PoisonPillRead"/> <preference for="Magento\Framework\MessageQueue\CallbackInvokerInterface" type="Magento\Framework\MessageQueue\CallbackInvoker"/> - <preference for="\Magento\Framework\GraphQl\Query\ErrorHandlerInterface" type="\Magento\Framework\GraphQl\Query\ErrorHandler"/> - <type name="\Magento\Framework\GraphQl\Query\ErrorHandler"> - <arguments> - <argument name="clientLogger" xsi:type="object">GraphQLClientLogger</argument> - <argument name="serverLogger" xsi:type="object">GraphQLServerLogger</argument> - <argument name="generalLogger" xsi:type="object">GraphQLGeneralLogger</argument> - <argument name="clientErrorCategories" xsi:type="array"> - <item name="graphql" xsi:type="const">\GraphQL\Error\Error::CATEGORY_GRAPHQL</item> - <item name="alreadyExists" xsi:type="const">\Magento\Framework\GraphQl\Exception\GraphQlAlreadyExistsException::EXCEPTION_CATEGORY</item> - <item name="authentication" xsi:type="const">\Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException::EXCEPTION_CATEGORY</item> - <item name="authorization" xsi:type="const">\Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException::EXCEPTION_CATEGORY</item> - <item name="input" xsi:type="const">\Magento\Framework\GraphQl\Exception\GraphQlInputException::EXCEPTION_CATEGORY</item> - <item name="noSuchEntity" xsi:type="const">\Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException::EXCEPTION_CATEGORY</item> - <item name="request" xsi:type="string">request</item> - <item name="user" xsi:type="string">user</item> - </argument> - <argument name="serverErrorCategories" xsi:type="array"> - <item name="internal" xsi:type="const">\GraphQL\Error\Error::CATEGORY_INTERNAL</item> - </argument> - </arguments> - </type> - <virtualType name="GraphQLClientLogger" type="Magento\Framework\Logger\Monolog"> - <arguments> - <argument name="handlers" xsi:type="array"> - <item name="error" xsi:type="object">GraphQLClientErrorHandler</item> - </argument> - </arguments> - </virtualType> - <virtualType name="GraphQLClientErrorHandler" type="\Magento\Framework\Logger\Handler\Base"> - <arguments> - <argument name="fileName" xsi:type="const">\Magento\Framework\GraphQl\Query\ErrorHandlerInterface::CLIENT_LOG_FILE</argument> - </arguments> - </virtualType> - <virtualType name="GraphQLServerLogger" type="Magento\Framework\Logger\Monolog"> - <arguments> - <argument name="handlers" xsi:type="array"> - <item name="error" xsi:type="object">GraphQLServerErrorHandler</item> - </argument> - </arguments> - </virtualType> - <virtualType name="GraphQLServerErrorHandler" type="\Magento\Framework\Logger\Handler\Base"> - <arguments> - <argument name="fileName" xsi:type="const">\Magento\Framework\GraphQl\Query\ErrorHandlerInterface::SERVER_LOG_FILE</argument> - </arguments> - </virtualType> - <virtualType name="GraphQLGeneralLogger" type="Magento\Framework\Logger\Monolog"> - <arguments> - <argument name="handlers" xsi:type="array"> - <item name="error" xsi:type="object">GraphQLGeneralErrorHandler</item> - </argument> - </arguments> - </virtualType> - <virtualType name="GraphQLGeneralErrorHandler" type="\Magento\Framework\Logger\Handler\Base"> - <arguments> - <argument name="fileName" xsi:type="const">\Magento\Framework\GraphQl\Query\ErrorHandlerInterface::GENERAL_LOG_FILE</argument> - </arguments> - </virtualType> </config> diff --git a/app/etc/graphql/di.xml b/app/etc/graphql/di.xml new file mode 100644 index 0000000000000..57b5687c660cb --- /dev/null +++ b/app/etc/graphql/di.xml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Framework\GraphQl\Query\ErrorHandlerInterface" type="Magento\Framework\GraphQl\Query\ErrorHandler"/> + <type name="Magento\Framework\GraphQl\Query\Resolver\ResolveLogger"> + <arguments> + <argument name="clientLogger" xsi:type="object">GraphQLClientLogger</argument> + <argument name="serverLogger" xsi:type="object">GraphQLServerLogger</argument> + </arguments> + </type> + <virtualType name="GraphQLClientLogger" type="Magento\Framework\Logger\Monolog"> + <arguments> + <argument name="handlers" xsi:type="array"> + <item name="error" xsi:type="object">GraphQLClientErrorHandler</item> + </argument> + </arguments> + </virtualType> + <virtualType name="GraphQLClientErrorHandler" type="Magento\Framework\Logger\Handler\Base"> + <arguments> + <argument name="fileName" xsi:type="const">Magento\Framework\GraphQl\Query\ErrorHandler::CLIENT_LOG_FILE</argument> + </arguments> + </virtualType> + <virtualType name="GraphQLServerLogger" type="Magento\Framework\Logger\Monolog"> + <arguments> + <argument name="handlers" xsi:type="array"> + <item name="error" xsi:type="object">GraphQLServerErrorHandler</item> + </argument> + </arguments> + </virtualType> + <virtualType name="GraphQLServerErrorHandler" type="Magento\Framework\Logger\Handler\Base"> + <arguments> + <argument name="fileName" xsi:type="const">Magento\Framework\GraphQl\Query\ErrorHandler::SERVER_LOG_FILE</argument> + </arguments> + </virtualType> + <preference for="Magento\Framework\GraphQl\Query\Resolver\ResolveLoggerInterface" type="Magento\Framework\GraphQl\Query\Resolver\ResolveLogger"/> +</config> diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php index fb5e921e72f6e..921314a50beff 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php @@ -8,99 +8,44 @@ namespace Magento\Framework\GraphQl\Query; use GraphQL\Error\ClientAware; -use GraphQL\Error\Error; -use Magento\Framework\Logger\Monolog; +use Magento\Framework\GraphQl\Query\Resolver\ResolveLoggerInterface; /** - * Class ErrorHandler + * @inheritDoc * * @package Magento\Framework\GraphQl\Query */ class ErrorHandler implements ErrorHandlerInterface { - /** - * @var Monolog - */ - private $clientLogger; - - /** - * @var Monolog - */ - private $serverLogger; + const SERVER_LOG_FILE = 'var/log/graphql/server/exception.log'; + const CLIENT_LOG_FILE = 'var/log/graphql/client/exception.log'; /** - * @var array + * @var ResolveLoggerInterface */ - private $clientErrorCategories; + private $resolveLogger; /** - * @var array - */ - private $serverErrorCategories; - - /** - * @var Monolog - */ - private $generalLogger; - - /** - * ErrorHandler constructor. - * - * @param Monolog $clientLogger - * @param Monolog $serverLogger - * @param Monolog $generalLogger - * @param array $clientErrorCategories - * @param array $serverErrorCategories - * - * @SuppressWarnings(PHPMD.LongVariable) + * @param ResolveLoggerInterface $resolveLogger */ public function __construct( - Monolog $clientLogger, - Monolog $serverLogger, - Monolog $generalLogger, - array $clientErrorCategories = [], - array $serverErrorCategories = [] + ResolveLoggerInterface $resolveLogger ) { - $this->clientLogger = $clientLogger; - $this->serverLogger = $serverLogger; - $this->generalLogger = $generalLogger; - $this->clientErrorCategories = $clientErrorCategories; - $this->serverErrorCategories = $serverErrorCategories; + $this->resolveLogger = $resolveLogger; } /** - * Handle errors - * - * @param Error[] $errors - * @param callable $formatter - * - * @return array + * @inheritDoc */ public function handle(array $errors, callable $formatter): array { return array_map( function (ClientAware $error) use ($formatter) { - $this->logError($error); + $this->resolveLogger->execute($error)->error($error); return $formatter($error); }, $errors ); } - - /** - * @param ClientAware $error - * - * @return boolean - */ - private function logError(ClientAware $error): bool - { - if (in_array($error->getCategory(), $this->clientErrorCategories)) { - return $this->clientLogger->error($error); - } elseif (in_array($error->getCategory(), $this->serverErrorCategories)) { - return $this->serverLogger->error($error); - } - - return $this->generalLogger->error($error); - } } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php index dbb149a865746..b89207fe3bdb1 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php @@ -12,20 +12,21 @@ /** * Interface ErrorHandlerInterface * - * @package Magento\Framework\GraphQl\Query + * GraphQL error handler + * + * @see \Magento\Framework\GraphQl\Query\QueryProcessor + * + * @api */ interface ErrorHandlerInterface { - const SERVER_LOG_FILE = 'var/log/graphql/server/exception.log'; - const CLIENT_LOG_FILE = 'var/log/graphql/client/exception.log'; - const GENERAL_LOG_FILE = 'var/log/graphql/exception.log'; /** * Handle errors * * @param Error[] $errors - * @param callable $formatter + * @param callable $formatter * * @return array */ - public function handle(array $errors, callable $formatter):array; + public function handle(array $errors, callable $formatter): array; } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php new file mode 100644 index 0000000000000..98e4f85b76128 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Query\Resolver; + +use GraphQL\Error\ClientAware; +use Psr\Log\LoggerInterface; + +/** + * @inheritDoc + */ +class ResolveLogger implements ResolveLoggerInterface +{ + /** + * @var LoggerInterface + */ + private $clientLogger; + + /** + * @var LoggerInterface + */ + private $serverLogger; + + /** + * @param LoggerInterface $clientLogger + * @param LoggerInterface $serverLogger + */ + public function __construct( + LoggerInterface $clientLogger, + LoggerInterface $serverLogger + ) { + $this->clientLogger = $clientLogger; + $this->serverLogger = $serverLogger; + } + + /** + * @inheritDoc + */ + public function execute(ClientAware $clientAware): LoggerInterface + { + return $clientAware->isClientSafe() ? + $this->clientLogger : + $this->serverLogger; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLoggerInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLoggerInterface.php new file mode 100644 index 0000000000000..ade416a3093bd --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLoggerInterface.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Query\Resolver; + +use GraphQL\Error\ClientAware; +use Psr\Log\LoggerInterface; + +/** + * Resolve which logger to use for certain ClientAware exception + * + * @api + */ +interface ResolveLoggerInterface +{ + /** + * Get logger to use for certain ClientAware exception + * + * @param ClientAware $clientAware + * + * @return LoggerInterface + */ + public function execute(ClientAware $clientAware): LoggerInterface; +} From 64184b3bf28b2a88ac691ca651d219ed74cf94ef Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Sun, 23 Jun 2019 13:59:29 +0100 Subject: [PATCH 065/841] Addedtests to cover store/default scenarios with getAttributeRawValue --- .../Model/ResourceModel/ProductTest.php | 85 +++++++++++++++++++ ...mple_with_custom_store_scope_attribute.php | 72 ++++++++++++++++ ..._custom_store_scope_attribute_rollback.php | 41 +++++++++ 3 files changed, 198 insertions(+) mode change 100644 => 100755 dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php create mode 100755 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php create mode 100755 dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php old mode 100644 new mode 100755 index 476f01eb277df..f073993f9b653 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php @@ -9,6 +9,10 @@ use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\StateException; class ProductTest extends TestCase { @@ -53,6 +57,87 @@ public function testGetAttributeRawValue() self::assertEquals($product->getName(), $actual); } + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + */ + public function testGetAttributeRawValueGetDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('default_value', $actual); + } + + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + */ + public function testGetAttributeRawValueGetStoreSpecificValueNoDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', null); + $this->productRepository->save($product); + + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 1, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'store_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('store_value', $actual); + } + + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + */ + public function testGetAttributeRawValueGetStoreSpecificValueWithDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); + $this->productRepository->save($product); + + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 1, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'store_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('store_value', $actual); + } + + /** + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + * @throws NoSuchEntityException + */ + public function testGetAttributeRawValueGetStoreValueFallbackToDefault() + { + $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true); + $product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); + $this->productRepository->save($product); + + $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1); + $this->assertEquals('default_value', $actual); + } + /** * @magentoAppArea adminhtml * @magentoDataFixture Magento/Catalog/_files/product_special_price.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php new file mode 100755 index 0000000000000..183d531f947e8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Eav\Model\Entity; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Type; + +/** @var \Magento\TestFramework\ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->get(ProductFactory::class); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); + + +/** @var $installer CategorySetup */ +$installer = $objectManager->create(CategorySetup::class); +$entityModel = $objectManager->create(Entity::class); +$attributeSetId = $installer->getAttributeSetId(Product::ENTITY, 'Default'); +$entityTypeId = $entityModel->setType(Product::ENTITY) + ->getTypeId(); +$groupId = $installer->getDefaultAttributeGroupId($entityTypeId, $attributeSetId); + +/** @var ProductAttributeInterface $attribute */ +$attribute = $objectManager->create(ProductAttributeInterface::class); + +$attribute->setAttributeCode('store_scoped_attribute_code') + ->setEntityTypeId($entityTypeId) + ->setIsVisible(true) + ->setFrontendInput('text') + ->setIsFilterable(1) + ->setIsUserDefined(1) + ->setUsedInProductListing(1) + ->setBackendType('varchar') + ->setIsUsedInGrid(1) + ->setIsVisibleInGrid(1) + ->setIsFilterableInGrid(1) + ->setFrontendLabel('nobody cares') + ->setAttributeGroupId($groupId) + ->setAttributeSetId(4); + +$attributeRepository->save($attribute); + +$product = $productFactory->create() + ->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setName('Simple With Store Scoped Custom Attribute') + ->setSku('simple_with_store_scoped_custom_attribute') + ->setPrice(100) + ->setVisibility(1) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_in_stock' => 1, + ] + ) + ->setStatus(1); +$product->setCustomAttribute('store_scoped_attribute_code', 'default_value'); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php new file mode 100755 index 0000000000000..54c832dd6a6ff --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; + +/** @var Magento\Framework\ObjectManagerInterface $objectManager */ +$objectManager = Bootstrap::getObjectManager(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +/** @var ProductAttributeRepositoryInterface $attributeRepository */ +$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +try { + /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ + $product = $productRepository->get('simple_with_store_scoped_custom_attribute'); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { +} + +try { + /** @var \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute */ + $attribute = $attributeRepository->get('store_scoped_attribute_code'); + $attributeRepository->delete($attribute); +} catch (NoSuchEntityException $e) { +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From 35c5185bdae84cae7ac5008098ae0d2d893cf05b Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Sun, 23 Jun 2019 18:02:10 +0100 Subject: [PATCH 066/841] minorcomment tweak to prevent testLiveCode code sniff test from failing --- .../Catalog/Model/ResourceModel/AbstractResource.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php old mode 100644 new mode 100755 index 0cd38b72b3da9..0399d022acb17 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -466,9 +466,9 @@ protected function _getOrigObject($object) * Checks also attribute's store scope: * We should insert on duplicate key update values if we unchecked 'STORE VIEW' checkbox in store view. * - * @param AbstractAttribute $attribute - * @param mixed $value New value of the attribute. - * @param array &$origData + * @param AbstractAttribute $attribute + * @param mixed $value New value of the attribute. + * @param array &$origData * @return bool */ protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, array &$origData) From f735a92003741c70a28d79862e67fc766c34a3f7 Mon Sep 17 00:00:00 2001 From: Sankalp Shekhar <shekhar.sankalp@gmail.com> Date: Tue, 4 Jun 2019 14:35:51 -0500 Subject: [PATCH 067/841] Minimize nPath Complexity for _validateRowForUpdate() Reduce Cyclomatic & NPath complexity for _prepareDataForUpdate() Move Row Data formatting to a new method Remove empty ElseIf statement Remove excessive Else-If branching Utilize an existing value to get entity table name Marked Unused fields as deprecated Breaking longer line into multiples to fix static tests failure Eliminate IfElse branching on _checkRowDuplicate() --- .../Model/Import/Address.php | 211 ++++++++++-------- 1 file changed, 120 insertions(+), 91 deletions(-) diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php index 7a1a09efaa7b6..7c6dfe12362cd 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php @@ -165,6 +165,7 @@ class Address extends AbstractCustomer * Array of region parameters * * @var array + * @deprecated field not in use */ protected $_regionParameters; @@ -194,16 +195,19 @@ class Address extends AbstractCustomer /** * @var \Magento\Eav\Model\Config + * @deprecated field not-in use */ protected $_eavConfig; /** * @var \Magento\Customer\Model\AddressFactory + * @deprecated not utilized anymore */ protected $_addressFactory; /** * @var \Magento\Framework\Stdlib\DateTime + * @deprecated the property isn't used */ protected $dateTime; @@ -419,10 +423,7 @@ protected function _getCustomerEntity() protected function _getNextEntityId() { if (!$this->_nextEntityId) { - /** @var $addressResource \Magento\Customer\Model\ResourceModel\Address */ - $addressResource = $this->_addressFactory->create()->getResource(); - $addressTable = $addressResource->getEntityTable(); - $this->_nextEntityId = $this->_resourceHelper->getNextAutoincrement($addressTable); + $this->_nextEntityId = $this->_resourceHelper->getNextAutoincrement($this->_entityTable); } return $this->_nextEntityId++; } @@ -587,7 +588,6 @@ protected function _mergeEntityAttributes(array $newAttributes, array $attribute */ protected function _prepareDataForUpdate(array $rowData):array { - $multiSeparator = $this->getMultipleValueSeparator(); $email = strtolower($rowData[self::COLUMN_EMAIL]); $customerId = $this->_getCustomerId($email, $rowData[self::COLUMN_WEBSITE]); // entity table data @@ -621,27 +621,18 @@ protected function _prepareDataForUpdate(array $rowData):array if (array_key_exists($attributeAlias, $rowData)) { $attributeParams = $this->adjustAttributeDataForWebsite($attributeParams, $websiteId); + $value = $rowData[$attributeAlias]; + if (!strlen($rowData[$attributeAlias])) { - if ($newAddress) { - $value = null; - } else { + if (!$newAddress) { continue; } - } elseif ($newAddress && !strlen($rowData[$attributeAlias])) { - } elseif (in_array($attributeParams['type'], ['select', 'boolean'])) { - $value = $this->getSelectAttrIdByValue($attributeParams, mb_strtolower($rowData[$attributeAlias])); - } elseif ('datetime' == $attributeParams['type']) { - $value = (new \DateTime())->setTimestamp(strtotime($rowData[$attributeAlias])); - $value = $value->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); - } elseif ('multiselect' == $attributeParams['type']) { - $ids = []; - foreach (explode($multiSeparator, mb_strtolower($rowData[$attributeAlias])) as $subValue) { - $ids[] = $this->getSelectAttrIdByValue($attributeParams, $subValue); - } - $value = implode(',', $ids); - } else { - $value = $rowData[$attributeAlias]; + + $value = null; + } elseif (in_array($attributeParams['type'], ['select', 'boolean', 'datetime', 'multiselect'])) { + $value = $this->getValueByAttributeType($rowData[$attributeAlias], $attributeParams); } + if ($attributeParams['is_static']) { $entityRow[$attributeAlias] = $value; } else { @@ -651,22 +642,18 @@ protected function _prepareDataForUpdate(array $rowData):array } foreach (self::getDefaultAddressAttributeMapping() as $columnName => $attributeCode) { if (!empty($rowData[$columnName])) { - /** @var $attribute \Magento\Eav\Model\Entity\Attribute\AbstractAttribute */ $table = $this->_getCustomerEntity()->getResource()->getTable('customer_entity'); $defaults[$table][$customerId][$attributeCode] = $addressId; } } // let's try to find region ID $entityRow['region_id'] = null; - if (!empty($rowData[self::COLUMN_REGION])) { - $countryNormalized = strtolower($rowData[self::COLUMN_COUNTRY_ID]); - $regionNormalized = strtolower($rowData[self::COLUMN_REGION]); - - if (isset($this->_countryRegions[$countryNormalized][$regionNormalized])) { - $regionId = $this->_countryRegions[$countryNormalized][$regionNormalized]; - $entityRow[self::COLUMN_REGION] = $this->_regions[$regionId]; - $entityRow['region_id'] = $regionId; - } + + if (!empty($rowData[self::COLUMN_REGION]) + && $this->getCountryRegionId($rowData[self::COLUMN_COUNTRY_ID], $rowData[self::COLUMN_REGION]) !== false) { + $regionId = $this->getCountryRegionId($rowData[self::COLUMN_COUNTRY_ID], $rowData[self::COLUMN_REGION]); + $entityRow[self::COLUMN_REGION] = $this->_regions[$regionId]; + $entityRow['region_id'] = $regionId; } if ($newAddress) { $entityRowNew = $entityRow; @@ -684,6 +671,39 @@ protected function _prepareDataForUpdate(array $rowData):array ]; } + /** + * Process row data, based on attirbute type + * + * @param string $rowAttributeData + * @param array $attributeParams + * @return \DateTime|int|string + * @throws \Exception + */ + protected function getValueByAttributeType(string $rowAttributeData, array $attributeParams) + { + $multiSeparator = $this->getMultipleValueSeparator(); + $value = $rowAttributeData; + switch ($attributeParams['type']) { + case 'select': + case 'boolean': + $value = $this->getSelectAttrIdByValue($attributeParams, mb_strtolower($rowAttributeData)); + break; + case 'datetime': + $value = (new \DateTime())->setTimestamp(strtotime($rowAttributeData)); + $value = $value->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT); + break; + case 'multiselect': + $ids = []; + foreach (explode($multiSeparator, mb_strtolower($rowAttributeData)) as $subValue) { + $ids[] = $this->getSelectAttrIdByValue($attributeParams, $subValue); + } + $value = implode(',', $ids); + break; + } + + return $value; + } + /** * Update and insert data in entity table * @@ -820,7 +840,6 @@ protected function _isOptionalAddressEmpty(array $rowData) * @param int $rowNumber * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _validateRowForUpdate(array $rowData, $rowNumber) { @@ -833,38 +852,36 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber) if ($customerId === false) { $this->addRowError(self::ERROR_CUSTOMER_NOT_FOUND, $rowNumber); + } elseif ($this->_checkRowDuplicate($customerId, $addressId)) { + $this->addRowError(self::ERROR_DUPLICATE_PK, $rowNumber); } else { - if ($this->_checkRowDuplicate($customerId, $addressId)) { - $this->addRowError(self::ERROR_DUPLICATE_PK, $rowNumber); - } else { - // check simple attributes - foreach ($this->_attributes as $attributeCode => $attributeParams) { - $websiteId = $this->_websiteCodeToId[$website]; - $attributeParams = $this->adjustAttributeDataForWebsite($attributeParams, $websiteId); + // check simple attributes + foreach ($this->_attributes as $attributeCode => $attributeParams) { + $websiteId = $this->_websiteCodeToId[$website]; + $attributeParams = $this->adjustAttributeDataForWebsite($attributeParams, $websiteId); - if (in_array($attributeCode, $this->_ignoredAttributes)) { - continue; - } - if (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) { - $this->isAttributeValid( - $attributeCode, - $attributeParams, - $rowData, - $rowNumber, - $multiSeparator - ); - } elseif ($attributeParams['is_required'] - && !$this->addressStorage->doesExist( - (string)$addressId, - (string)$customerId - ) - ) { - $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode); - } + if (in_array($attributeCode, $this->_ignoredAttributes)) { + continue; + } elseif (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) { + $this->isAttributeValid( + $attributeCode, + $attributeParams, + $rowData, + $rowNumber, + $multiSeparator + ); + } elseif ($attributeParams['is_required'] + && !$this->addressStorage->doesExist( + (string)$addressId, + (string)$customerId + ) + ) { + $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode); } + } + if (isset($rowData[self::COLUMN_COUNTRY_ID])) { if (isset($rowData[self::COLUMN_POSTCODE]) - && isset($rowData[self::COLUMN_COUNTRY_ID]) && !$this->postcodeValidator->isValid( $rowData[self::COLUMN_COUNTRY_ID], $rowData[self::COLUMN_POSTCODE] @@ -873,19 +890,14 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber) $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, self::COLUMN_POSTCODE); } - if (isset($rowData[self::COLUMN_COUNTRY_ID]) && isset($rowData[self::COLUMN_REGION])) { - $countryRegions = isset( - $this->_countryRegions[strtolower($rowData[self::COLUMN_COUNTRY_ID])] - ) ? $this->_countryRegions[strtolower( - $rowData[self::COLUMN_COUNTRY_ID] - )] : []; - - if (!empty($rowData[self::COLUMN_REGION]) && !empty($countryRegions) && !isset( - $countryRegions[strtolower($rowData[self::COLUMN_REGION])] + if (isset($rowData[self::COLUMN_REGION]) + && !empty($rowData[self::COLUMN_REGION]) + && false === $this->getCountryRegionId( + $rowData[self::COLUMN_COUNTRY_ID], + $rowData[self::COLUMN_REGION] ) - ) { - $this->addRowError(self::ERROR_INVALID_REGION, $rowNumber, self::COLUMN_REGION); - } + ) { + $this->addRowError(self::ERROR_INVALID_REGION, $rowNumber, self::COLUMN_REGION); } } } @@ -909,15 +921,13 @@ protected function _validateRowForDelete(array $rowData, $rowNumber) $customerId = $this->_getCustomerId($email, $website); if ($customerId === false) { $this->addRowError(self::ERROR_CUSTOMER_NOT_FOUND, $rowNumber); - } else { - if (!strlen($addressId)) { - $this->addRowError(self::ERROR_ADDRESS_ID_IS_EMPTY, $rowNumber); - } elseif (!$this->addressStorage->doesExist( - (string)$addressId, - (string)$customerId - )) { - $this->addRowError(self::ERROR_ADDRESS_NOT_FOUND, $rowNumber); - } + } elseif (!strlen($addressId)) { + $this->addRowError(self::ERROR_ADDRESS_ID_IS_EMPTY, $rowNumber); + } elseif (!$this->addressStorage->doesExist( + (string)$addressId, + (string)$customerId + )) { + $this->addRowError(self::ERROR_ADDRESS_NOT_FOUND, $rowNumber); } } } @@ -931,19 +941,18 @@ protected function _validateRowForDelete(array $rowData, $rowNumber) */ protected function _checkRowDuplicate($customerId, $addressId) { - if ($this->addressStorage->doesExist( + $isAddressExists = $this->addressStorage->doesExist( (string)$addressId, (string)$customerId - )) { - if (!isset($this->_importedRowPks[$customerId][$addressId])) { - $this->_importedRowPks[$customerId][$addressId] = true; - return false; - } else { - return true; - } - } else { - return false; + ); + + $isPkRowSet = isset($this->_importedRowPks[$customerId][$addressId]); + + if ($isAddressExists && !$isPkRowSet) { + $this->_importedRowPks[$customerId][$addressId] = true; } + + return $isAddressExists && $isPkRowSet; } /** @@ -957,4 +966,24 @@ public function setCustomerAttributes($customerAttributes) $this->_customerAttributes = $customerAttributes; return $this; } + + /** + * Get RegionID from the initialized data + * + * @param string $countryId + * @param string $region + * @return bool|int + */ + private function getCountryRegionId(string $countryId, string $region) + { + $countryNormalized = strtolower($countryId); + $regionNormalized = strtolower($region); + + if (isset($this->_countryRegions[$countryNormalized]) + && isset($this->_countryRegions[$countryNormalized][$regionNormalized])) { + return $this->_countryRegions[$countryNormalized][$regionNormalized]; + } + + return false; + } } From 72d5aa84e0b452eb018ae0288e2bbcde2307c7de Mon Sep 17 00:00:00 2001 From: Rafael Kassner <kassner@gmail.com> Date: Mon, 24 Jun 2019 10:02:32 +0200 Subject: [PATCH 068/841] Use variadics instead of func_get_args() --- lib/internal/Magento/Framework/Phrase/__.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/Phrase/__.php b/lib/internal/Magento/Framework/Phrase/__.php index 6f3186231e3bb..a971df585df2d 100644 --- a/lib/internal/Magento/Framework/Phrase/__.php +++ b/lib/internal/Magento/Framework/Phrase/__.php @@ -10,10 +10,8 @@ * @SuppressWarnings(PHPMD.ShortMethodName) * @return \Magento\Framework\Phrase */ -function __() +function __(...$argc) { - $argc = func_get_args(); - $text = array_shift($argc); if (!empty($argc) && is_array($argc[0])) { $argc = $argc[0]; From b6a98b3e02b1a61386f1ec7ee97f3ed32ee408f2 Mon Sep 17 00:00:00 2001 From: natalia <natalia_marozava@epam.com> Date: Mon, 24 Jun 2019 12:31:43 +0300 Subject: [PATCH 069/841] MC-17251: Creating a preference for category product indexer breaks setup:di:compile --- .../Catalog/Model/Indexer/Category/Product/Action/Full.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 eb59acb56c356..c5a9c78c42383 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 @@ -150,9 +150,9 @@ private function switchTables(): void /** * Refresh entities index * - * @return $this + * @return Full */ - public function execute(): self + public function execute(): Full { $this->createTables(); $this->clearReplicaTables(); From 1fc76514753a9eecf0385a7d82de15e308d5b333 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Mon, 24 Jun 2019 14:58:08 +0300 Subject: [PATCH 070/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" - Add activate and diactivate rules --- app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml | 2 ++ app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml | 2 ++ 9 files changed, 18 insertions(+) diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml index 51297a96438d2..fc7fba8a9d3b4 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml @@ -193,6 +193,8 @@ <argument name="wps_other">wps_other</argument> <argument name="payflow_link_ca">payflow_link_ca</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml index 28cc075e0c619..5c921ae1b1f63 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml @@ -85,6 +85,8 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml index 7f1fcc08334fe..0a7bcc374fe65 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml @@ -85,6 +85,8 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml index d8b765b9b4d22..04c969b59885e 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml @@ -85,6 +85,8 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_express">wps_express</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml index 50ce14e66ee0c..c1183b53a5eb7 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml @@ -85,6 +85,8 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml index de059dcc59c39..feb9ff11f4cff 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml @@ -85,6 +85,8 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml index d9fc7ef3f201c..77bb13fac8cb9 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml @@ -85,6 +85,8 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml index c5b8b09c3a2cf..541c531ca18c5 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml @@ -85,6 +85,8 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml index b7924e770aa22..47faec9b51ef8 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml @@ -414,6 +414,8 @@ <argument name="paypal_payflowpro_with_express_checkout">paypal_payflowpro_with_express_checkout</argument> <argument name="payflow_link_us">payflow_link_us</argument> </rule> + <rule type="simpleMarkEnable" event="activate-rule"/> + <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> <rule type="inContextShowMerchantId" event="activate-rule"/> From d9c3e7bb31800f11b93410b347f0452fceea9c40 Mon Sep 17 00:00:00 2001 From: Krissy Hiserote <khiserote@magento.com> Date: Mon, 24 Jun 2019 10:09:07 -0500 Subject: [PATCH 071/841] MC-16650: Product Attribute Type Price Not Displaying - use price filter for all attributes with catalog input type = 'price' --- .../Catalog/Model/Layer/FilterList.php | 10 ++++-- .../Test/Unit/Model/Layer/FilterListTest.php | 8 +++-- .../Model/Layer/Filter/Price.php | 5 ++- .../Model/Search/RequestGenerator/Decimal.php | 32 +------------------ .../Unit/Model/Layer/Filter/PriceTest.php | 7 ++++ .../Model/Layer/Filter/PriceTest.php | 8 +++++ 6 files changed, 33 insertions(+), 37 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Layer/FilterList.php b/app/code/Magento/Catalog/Model/Layer/FilterList.php index 9d7b71c981c6b..7f06c97d3e8d9 100644 --- a/app/code/Magento/Catalog/Model/Layer/FilterList.php +++ b/app/code/Magento/Catalog/Model/Layer/FilterList.php @@ -3,9 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Model\Layer; +use Magento\Catalog\Model\Product\Attribute\Backend\Price; + +/** + * Layer navigation filters + */ class FilterList { const CATEGORY_FILTER = 'category'; @@ -106,9 +112,9 @@ protected function getAttributeFilterClass(\Magento\Catalog\Model\ResourceModel\ { $filterClassName = $this->filterTypes[self::ATTRIBUTE_FILTER]; - if ($attribute->getAttributeCode() == 'price') { + if ($attribute->getBackendModel() === Price::class) { $filterClassName = $this->filterTypes[self::PRICE_FILTER]; - } elseif ($attribute->getBackendType() == 'decimal') { + } elseif ($attribute->getBackendType() === 'decimal') { $filterClassName = $this->filterTypes[self::DECIMAL_FILTER]; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php index 8733f305ce091..2d3c764cb6907 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php @@ -3,10 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Test\Unit\Model\Layer; use \Magento\Catalog\Model\Layer\FilterList; +use Magento\Catalog\Model\Product\Attribute\Backend\Price; class FilterListTest extends \PHPUnit\Framework\TestCase { @@ -95,8 +97,8 @@ public function getFiltersDataProvider() { return [ [ - 'method' => 'getAttributeCode', - 'value' => FilterList::PRICE_FILTER, + 'method' => 'getBackendModel', + 'value' => Price::class, 'expectedClass' => 'PriceFilterClass', ], [ @@ -105,7 +107,7 @@ public function getFiltersDataProvider() 'expectedClass' => 'DecimalFilterClass', ], [ - 'method' => 'getAttributeCode', + 'method' => 'getBackendModel', 'value' => null, 'expectedClass' => 'AttributeFilterClass', ] diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php index a19f53469ae01..66d9281ed38e2 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Price.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogSearch\Model\Layer\Filter; use Magento\Catalog\Model\Layer\Filter\AbstractFilter; @@ -11,6 +13,7 @@ * Layer price filter based on Search API * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Price extends AbstractFilter { @@ -138,7 +141,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) list($from, $to) = $filter; $this->getLayer()->getProductCollection()->addFieldToFilter( - 'price', + $this->getAttributeModel()->getAttributeCode(), ['from' => $from, 'to' => empty($to) || $from == $to ? $to : $to - self::PRICE_DELTA] ); diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php index c9f738b07c175..73d011cc532db 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Decimal.php @@ -10,29 +10,12 @@ use Magento\Catalog\Model\ResourceModel\Eav\Attribute; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\Search\Request\FilterInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\App\ObjectManager; -use Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory; -use Magento\Store\Model\ScopeInterface; /** * Catalog search range request generator. */ class Decimal implements GeneratorInterface { - /** - * @var \Magento\Store\Model\ScopeInterface - */ - private $scopeConfig; - - /** - * @param \Magento\Store\Model\ScopeInterface|null $scopeConfig - */ - public function __construct(ScopeConfigInterface $scopeConfig = null) - { - $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); - } - /** * @inheritdoc */ @@ -56,21 +39,8 @@ public function getAggregationData(Attribute $attribute, $bucketName) 'type' => BucketInterface::TYPE_DYNAMIC, 'name' => $bucketName, 'field' => $attribute->getAttributeCode(), - 'method' => $this->getRangeCalculation(), + 'method' => 'manual', 'metric' => [['type' => 'count']], ]; } - - /** - * Get range calculation by what was set in the configuration - * - * @return string - */ - private function getRangeCalculation(): string - { - return $this->scopeConfig->getValue( - AlgorithmFactory::XML_PATH_RANGE_CALCULATION, - ScopeInterface::SCOPE_STORE - ); - } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php index abad58a6876d3..f783f75a170e3 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/PriceTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CatalogSearch\Test\Unit\Model\Layer\Filter; @@ -208,6 +209,12 @@ public function testApply() $priceId = '15-50'; $requestVar = 'test_request_var'; + $this->target->setAttributeModel($this->attribute); + $attributeCode = 'price'; + $this->attribute->expects($this->any()) + ->method('getAttributeCode') + ->will($this->returnValue($attributeCode)); + $this->target->setRequestVar($requestVar); $this->request->expects($this->exactly(1)) ->method('getParam') diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php index 451553113af2c..a7944566eb8e0 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/PriceTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogSearch\Model\Layer\Filter; use Magento\TestFramework\Helper\Bootstrap; @@ -35,10 +37,16 @@ protected function setUp() $category->load(4); $layer = $this->objectManager->get(\Magento\Catalog\Model\Layer\Category::class); $layer->setCurrentCategory($category); + /** @var $attribute \Magento\Catalog\Model\Entity\Attribute */ + $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\Entity\Attribute::class + ); + $attribute->loadByCode('catalog_product', 'price'); $this->_model = $this->objectManager->create( \Magento\CatalogSearch\Model\Layer\Filter\Price::class, ['layer' => $layer] ); + $this->_model->setAttributeModel($attribute); } public function testApplyNothing() From 77f0b86f1f3d10668b788eb6f854c95670c6753e Mon Sep 17 00:00:00 2001 From: Hailong Zhao <hailongzh@hotmail.com> Date: Mon, 24 Jun 2019 11:38:36 -0400 Subject: [PATCH 072/841] Remove the added constants to fix the Semantic Version Checker build warnings. --- app/code/Magento/Sales/Model/Order/Email/Sender.php | 8 +------- .../Magento/Sales/Model/Order/Email/SenderBuilder.php | 2 +- .../Model/Order/Creditmemo/Sender/EmailSenderTest.php | 2 +- .../Model/Order/Email/Sender/CreditmemoSenderTest.php | 2 +- .../Unit/Model/Order/Email/Sender/InvoiceSenderTest.php | 2 +- .../Unit/Model/Order/Email/Sender/OrderSenderTest.php | 4 ++-- .../Unit/Model/Order/Email/Sender/ShipmentSenderTest.php | 2 +- .../Unit/Model/Order/Invoice/Sender/EmailSenderTest.php | 2 +- .../Unit/Model/Order/Shipment/Sender/EmailSenderTest.php | 2 +- 9 files changed, 10 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender.php b/app/code/Magento/Sales/Model/Order/Email/Sender.php index b437c3f5fbf72..529da68db5346 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender.php @@ -18,12 +18,6 @@ */ abstract class Sender { - /** - * Copy methods - */ - const COPY_METHOD_BCC = 'bcc'; - const COPY_METHOD_COPY = 'copy'; - /** * @var \Magento\Sales\Model\Order\Email\SenderBuilderFactory */ @@ -93,7 +87,7 @@ protected function checkAndSend(Order $order) $this->logger->error($e->getMessage()); return false; } - if ($this->identityContainer->getCopyMethod() == self::COPY_METHOD_COPY) { + if ($this->identityContainer->getCopyMethod() == 'copy') { try { $sender->sendCopyTo(); } catch (\Exception $e) { diff --git a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php index 788baa05cadb0..1144b7a0fe383 100644 --- a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php +++ b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php @@ -65,7 +65,7 @@ public function send() $copyTo = $this->identityContainer->getEmailCopyTo(); - if (!empty($copyTo) && $this->identityContainer->getCopyMethod() == Sender::COPY_METHOD_BCC) { + if (!empty($copyTo) && $this->identityContainer->getCopyMethod() == 'bcc') { foreach ($copyTo as $email) { $this->transportBuilder->addBcc($email); } diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php index 09cf7f387449f..13ed0739348b2 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php @@ -285,7 +285,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending if ($emailSendingResult) { $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->subject::COPY_METHOD_COPY); + ->willReturn('copy'); $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php index 442f7892cdbd3..8047a5e71361f 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php @@ -140,7 +140,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema if ($emailSendingResult) { $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->sender::COPY_METHOD_COPY); + ->willReturn('copy'); $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php index 20f63aeac147a..212fccc55538b 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php @@ -146,7 +146,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema if ($emailSendingResult) { $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->sender::COPY_METHOD_COPY); + ->willReturn('copy'); $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php index c6399731f2745..8adebfc99a47f 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php @@ -79,7 +79,7 @@ public function testSend($configValue, $forceSyncMode, $emailSendingResult, $sen if ($emailSendingResult) { $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->sender::COPY_METHOD_COPY); + ->willReturn('copy'); $addressMock = $this->createMock(\Magento\Sales\Model\Order\Address::class); @@ -220,7 +220,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->sender::COPY_METHOD_COPY); + ->willReturn('copy'); $addressMock = $this->createMock(\Magento\Sales\Model\Order\Address::class); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php index b86b02914e17d..fc7796f63cdab 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php @@ -146,7 +146,7 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema if ($emailSendingResult) { $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->sender::COPY_METHOD_COPY); + ->willReturn('copy'); $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php index fd090197a01af..6db1ec0392e0e 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php @@ -284,7 +284,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending if ($emailSendingResult) { $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->subject::COPY_METHOD_COPY); + ->willReturn('copy'); $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php index 91beca535ff27..2262fbf03c1a1 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php @@ -286,7 +286,7 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending if ($emailSendingResult) { $this->identityContainerMock->expects($this->once()) ->method('getCopyMethod') - ->willReturn($this->subject::COPY_METHOD_COPY); + ->willReturn('copy'); $this->senderBuilderFactoryMock->expects($this->once()) ->method('create') From 9ae33c855dd68bbdfcfe4148facaaa9c0c4b30a1 Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Mon, 24 Jun 2019 22:44:20 +0100 Subject: [PATCH 073/841] Revert "minorcomment tweak to prevent testLiveCode code sniff test from failing" This reverts commit 35c5185bdae84cae7ac5008098ae0d2d893cf05b. --- .../Catalog/Model/ResourceModel/AbstractResource.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100755 => 100644 app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php old mode 100755 new mode 100644 index 0399d022acb17..0cd38b72b3da9 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -466,9 +466,9 @@ protected function _getOrigObject($object) * Checks also attribute's store scope: * We should insert on duplicate key update values if we unchecked 'STORE VIEW' checkbox in store view. * - * @param AbstractAttribute $attribute - * @param mixed $value New value of the attribute. - * @param array &$origData + * @param AbstractAttribute $attribute + * @param mixed $value New value of the attribute. + * @param array &$origData * @return bool */ protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, array &$origData) From 8c4bfa62528235b651a06e9a24f4a8a0b3a5f7f9 Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Mon, 24 Jun 2019 22:48:52 +0100 Subject: [PATCH 074/841] Removing & from param for _canUpdateAttribute as it's breaking static tests --- .../Magento/Catalog/Model/ResourceModel/AbstractResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php index 0cd38b72b3da9..2680b84653e41 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -468,7 +468,7 @@ protected function _getOrigObject($object) * * @param AbstractAttribute $attribute * @param mixed $value New value of the attribute. - * @param array &$origData + * @param array $origData * @return bool */ protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, array &$origData) From 7b0e528a316af06dac9d6327db25ed53065a0893 Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Mon, 24 Jun 2019 23:40:12 +0100 Subject: [PATCH 075/841] Added class comment ProductTest to remove static test warning --- .../Magento/Catalog/Model/ResourceModel/ProductTest.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php index f073993f9b653..e218c508b7d3e 100755 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php @@ -14,6 +14,12 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\StateException; +/** + * Tests product resource model + * + * @see \Magento\Catalog\Model\ResourceModel\Product + * @see \Magento\Catalog\Model\ResourceModel\AbstractResource + */ class ProductTest extends TestCase { /** From c34f83cb30a5960a62dbeff562fdbf7215d616a0 Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Mon, 24 Jun 2019 23:41:01 +0100 Subject: [PATCH 076/841] Changed usage of sizeof to count to remove usage of discouraged sizeof function in getAttributeRawValue --- .../Magento/Catalog/Model/ResourceModel/AbstractResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php index 2680b84653e41..b7665c176dfad 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -617,7 +617,7 @@ public function getAttributeRawValue($entityId, $attribute, $store) } } - if (is_array($attributesData) && sizeof($attributesData) == 1) { + if (is_array($attributesData) && count($attributesData) == 1) { $attributesData = array_shift($attributesData); } From 9fe43844e09517ca6d4e58c786605af871463d59 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Tue, 25 Jun 2019 17:33:36 +0400 Subject: [PATCH 077/841] MAGETWO-70803: [GITHUB] Inconsistent CSV file Import error: #7495 - Added automated test script --- .../AdminImportProductsActionGroup.xml | 7 ++ .../Mftf/Section/AdminImportMainSection.xml | 2 + ...ImportCSVFileCorrectDifferentFilesTest.xml | 47 +++++++ .../acceptance/tests/_data/BB-Products.csv | 118 ++++++++++++++++++ .../tests/_data/BB-ProductsWorking.csv | 29 +++++ 5 files changed, 203 insertions(+) create mode 100644 app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml create mode 100644 dev/tests/acceptance/tests/_data/BB-Products.csv create mode 100644 dev/tests/acceptance/tests/_data/BB-ProductsWorking.csv diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml index a9100b4730b8c..13843dc804655 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml @@ -27,4 +27,11 @@ <waitForPageLoad stepKey="AdminMessagesSection"/> <see selector="{{AdminMessagesSection.notice}}" userInput="{{importMessage}}" stepKey="seeImportMessage"/> </actionGroup> + <actionGroup name="checkDataForImportProductActionGroup" extends="AdminImportProductsActionGroup"> + <remove keyForRemoval="clickImportButton"/> + <remove keyForRemoval="AdminImportMainSectionLoad2"/> + <remove keyForRemoval="assertSuccessMessage"/> + <remove keyForRemoval="AdminMessagesSection"/> + <remove keyForRemoval="seeImportMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml index 2ce6b1e35777f..6f6d6e4e6b8ac 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml @@ -13,5 +13,7 @@ <element name="importBehavior" type="select" selector="#basic_behavior"/> <element name="selectFileToImport" type="input" selector="#import_file"/> <element name="importButton" type="button" selector="#import_validation_container button" timeout="30"/> + <element name="messageSuccess" type="text" selector=".messages div.message-success"/> + <element name="messageError" type="text" selector=".messages div.message-error"/> </section> </sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml new file mode 100644 index 0000000000000..349d3415af06e --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminProductImportCSVFileCorrectDifferentFilesTest"> + <annotations> + <description value="Product import from CSV file correct from different files."/> + <features value="Import/Export"/> + <title value="Product import from CSV file correct from different files."/> + <severity value="MAJOR"/> + <testCaseId value="MC-17104"/> + <useCaseId value="MAGETWO-70803"/> + <group value="importExport"/> + </annotations> + <before> + <!--Login as Admin--> + <comment userInput="Login as Admin" stepKey="commentLogin"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!--Logout from Admin--> + <comment userInput="Logout from Admin" stepKey="commentLogout"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <!--Check data products with add/update behavior--> + <comment userInput="Check data products with add/update behavior" stepKey="commentCheckData"/> + <actionGroup ref="checkDataForImportProductActionGroup" stepKey="adminImportProducts"> + <argument name="behavior" value="Add/Update"/> + <argument name="importFile" value="BB-ProductsWorking.csv"/> + <argument name="importMessage" value="Checked rows: 28, checked entities: 28, invalid rows: 0, total errors: 0"/> + </actionGroup> + <see selector="{{AdminImportMainSection.messageSuccess}}" userInput='File is valid! To start import process press "Import" button' stepKey="seeSuccessMessage"/> + <actionGroup ref="checkDataForImportProductActionGroup" stepKey="adminImportProducts1"> + <argument name="behavior" value="Add/Update"/> + <argument name="importFile" value="BB-Products.csv"/> + <argument name="importMessage" value="Checked rows: 117, checked entities: 115, invalid rows: 2, total errors: 2"/> + <argument name="checkMessage" value='Curly quotes used instead of straight quotes in row(s): 84, 85'/> + </actionGroup> + <see selector="{{AdminImportMainSection.messageError}}" userInput='Curly quotes used instead of straight quotes in row(s): 84, 85' stepKey="seeErrorMessage"/> + </test> +</tests> diff --git a/dev/tests/acceptance/tests/_data/BB-Products.csv b/dev/tests/acceptance/tests/_data/BB-Products.csv new file mode 100644 index 0000000000000..7ab03fd5eaeda --- /dev/null +++ b/dev/tests/acceptance/tests/_data/BB-Products.csv @@ -0,0 +1,118 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus +BB-D2010129,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Sistemi di Climatizzazione,Default Category/Casa Giardino/Sistemi di Climatizzazione/Aria condizionata e ventilatori",base,"Ventilatore Portatile Spray FunFan Nero","<a id=""maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""><p>Se</a> sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il <strong>ventilatore portatile spray FunFan</strong>. Si tratta di una soluzione pratica per mantenersi al fresco in una moltitudine di situazioni, come escursioni, gite in spiaggia, mentre si fa sport, ecc. Inoltre, grazie alle sue dimensioni ridotte (dimensioni: circa 9 x 26 x 6,5 cm) e peso ridotto (circa 130 g), lo puoi portare ovunque!<br /><br /><a href=""http://www.myfunfan.com""><strong>www.myfunfan.com</strong></a><br /><br />Questo <strong>ventilatore portatile</strong> originale ha un pulsante per attivare le eliche in PVC malleabili e una leva che spruzza l'acqua. Cosa c'è di più, puoi aggiungere il ghiaccio per aumentare la sensazione di freddo! Include 1 cacciavite a croce per inserire le batterie. Realizzato in PVC. Funzionamento a batterie (2 x AA, non incluse).</p><p> Dimenzioni per Ventilatore Portatile Spray FunFan : </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 28 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.185 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888101772</p>","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""> Maggiori Informazioni</a>",0.185,1,"Taxable Goods","Catalog, Search",19.9,,,,Ventilatore-Portatile-Spray-FunFan-Nero,"Ventilatore Portatile Spray FunFan Nero","Casa Giardino,Casa,Giardino,Sistemi di Climatizzazione,Sistemi,Climatizzazione,Aria condizionata e ventilatori,Aria,condizionata,ventilatori,Colore Nero,Nero,","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan",http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,,http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,,,,,,"2016-01-12 09:56:53",,,,,,,,,,,,,,,,,"GTIN=4899888101772",41,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78889.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78888.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78886.jpg","GTIN=4899888101772",,,,,,,,,, +BB-D2010130,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Sistemi di Climatizzazione,Default Category/Casa Giardino/Sistemi di Climatizzazione/Aria condizionata e ventilatori",base,"Ventilatore Portatile Spray FunFan Bianco","<a id=""maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""><p>Se</a> sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il <strong>ventilatore portatile spray FunFan</strong>. Si tratta di una soluzione pratica per mantenersi al fresco in una moltitudine di situazioni, come escursioni, gite in spiaggia, mentre si fa sport, ecc. Inoltre, grazie alle sue dimensioni ridotte (dimensioni: circa 9 x 26 x 6,5 cm) e peso ridotto (circa 130 g), lo puoi portare ovunque!<br /><br /><a href=""http://www.myfunfan.com""><strong>www.myfunfan.com</strong></a><br /><br />Questo <strong>ventilatore portatile</strong> originale ha un pulsante per attivare le eliche in PVC malleabili e una leva che spruzza l'acqua. Cosa c'è di più, puoi aggiungere il ghiaccio per aumentare la sensazione di freddo! Include 1 cacciavite a croce per inserire le batterie. Realizzato in PVC. Funzionamento a batterie (2 x AA, non incluse).</p><p> Dimenzioni per Ventilatore Portatile Spray FunFan : </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 28 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.185 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888107965</p>","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""> Maggiori Informazioni</a>",0.185,1,"Taxable Goods","Catalog, Search",19.9,,,,Ventilatore-Portatile-Spray-FunFan-Bianco,"Ventilatore Portatile Spray FunFan Bianco","Casa Giardino,Casa,Giardino,Sistemi di Climatizzazione,Sistemi,Climatizzazione,Aria condizionata e ventilatori,Aria,condizionata,ventilatori,Colore Bianco,Bianco,","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan",http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,,http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,,,,,,"2016-01-12 09:56:53",,,,,,,,,,,,,,,,,"GTIN=4899888107965",741,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78889.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78888.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78886.jpg","GTIN=4899888107965",,,,,,,,,, +BB-D2010131,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Sistemi di Climatizzazione,Default Category/Casa Giardino/Sistemi di Climatizzazione/Aria condizionata e ventilatori",base,"Ventilatore Portatile Spray FunFan Rosso","<a id=""maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""><p>Se</a> sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il <strong>ventilatore portatile spray FunFan</strong>. Si tratta di una soluzione pratica per mantenersi al fresco in una moltitudine di situazioni, come escursioni, gite in spiaggia, mentre si fa sport, ecc. Inoltre, grazie alle sue dimensioni ridotte (dimensioni: circa 9 x 26 x 6,5 cm) e peso ridotto (circa 130 g), lo puoi portare ovunque!<br /><br /><a href=""http://www.myfunfan.com""><strong>www.myfunfan.com</strong></a><br /><br />Questo <strong>ventilatore portatile</strong> originale ha un pulsante per attivare le eliche in PVC malleabili e una leva che spruzza l'acqua. Cosa c'è di più, puoi aggiungere il ghiaccio per aumentare la sensazione di freddo! Include 1 cacciavite a croce per inserire le batterie. Realizzato in PVC. Funzionamento a batterie (2 x AA, non incluse).</p><p> Dimenzioni per Ventilatore Portatile Spray FunFan : </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 28 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.185 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888107972</p>","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""> Maggiori Informazioni</a>",0.185,1,"Taxable Goods","Catalog, Search",19.9,,,,Ventilatore-Portatile-Spray-FunFan-Rosso,"Ventilatore Portatile Spray FunFan Rosso","Casa Giardino,Casa,Giardino,Sistemi di Climatizzazione,Sistemi,Climatizzazione,Aria condizionata e ventilatori,Aria,condizionata,ventilatori,Colore Rosso,Rosso,","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan",http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,,http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,,,,,,"2016-01-12 09:56:53",,,,,,,,,,,,,,,,,"GTIN=4899888107972",570,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78889.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78888.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78886.jpg","GTIN=4899888107972",,,,,,,,,, +BB-H1000163,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Sedia Pieghevole Campart Travel CH0592 Blu Marino","<a id=""maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""><p>Se</a> stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la <strong>sedia pieghevole </strong><strong>Campart Travel</strong>! Questa <strong>sedia da campeggio imbottita</strong> è perfetta per i luoghi di campeggio, cortili, giardini, ecc. Ideale per il riposo e il relax. Può portare fino a 120 kg. Dimensioni: 66 x 70 / 120 x 87 / 115 cm circa. Semplice da trasportare ovunque, grazie al suo design funzionale ed elegante (dimensioni quando piegato: circa 66 x 110 x 10 cm). 7 posizioni regolabili e un poggiatesta incorporato. Struttura in alluminio e stoffa imbottita in poliestere. Altezza sedia: circa 50 cm.</p><p> Dimenzioni per Sedia Pieghevole Campart Travel: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 66 Cm</li><li>Profondita': 110 Cm</li><li>Peso: 5.3 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005922</p>","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc.</br><a href=""#maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""> Maggiori Informazioni</a>",5.3,1,"Taxable Goods","Catalog, Search",129,,,,Sedia-Pieghevole-Campart-Travel-CH0592 Blu Marino,"Sedia Pieghevole Campart Travel CH0592 Blu Marino","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0592 Blu Marino,CH0592 Blu Marino,","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc",http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_00.jpg,,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_00.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005922",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_02.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_04.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_03.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_01.jpg","GTIN=8713016005922",,,,,,,,,, +BB-H1000162,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Sedia Pieghevole Campart Travel CH0596 Grigio","<a id=""maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""><p>Se</a> stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la <strong>sedia pieghevole </strong><strong>Campart Travel</strong>! Questa <strong>sedia da campeggio imbottita</strong> è perfetta per i luoghi di campeggio, cortili, giardini, ecc. Ideale per il riposo e il relax. Può portare fino a 120 kg. Dimensioni: 66 x 70 / 120 x 87 / 115 cm circa. Semplice da trasportare ovunque, grazie al suo design funzionale ed elegante (dimensioni quando piegato: circa 66 x 110 x 10 cm). 7 posizioni regolabili e un poggiatesta incorporato. Struttura in alluminio e stoffa imbottita in poliestere. Altezza sedia: circa 50 cm.</p><p> Dimenzioni per Sedia Pieghevole Campart Travel: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 66 Cm</li><li>Profondita': 110 Cm</li><li>Peso: 5.3 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005960</p>","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc.</br><a href=""#maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""> Maggiori Informazioni</a>",5.3,1,"Taxable Goods","Catalog, Search",129,,,,Sedia-Pieghevole-Campart-Travel-CH0596 Grigio,"Sedia Pieghevole Campart Travel CH0596 Grigio","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0596 Grigio,CH0596 Grigio,","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc",http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_02.jpg,,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_02.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005960",2,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_00.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_04.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_03.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_01.jpg","GTIN=8713016005960",,,,,,,,,, +BB-F1520329,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Poggiapiedi Pieghevole Campart Travel CH0593 Blu Marino","<a id=""maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""><p>Approfitta</a> di un'esperienza rilassante con l'aiuto del <strong>poggiapiedi pieghevole Campart Travel</strong>! Questo<strong> poggiapiedi imbottito</strong> è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc. Possiede 2 ganci di circa 3 cm di diametro che possono essere facilmente attaccate alla barra inferiore delle sedie (utilizzabile solo per sedie con una barra inferiore di circa 2 cm di diametro). Struttura in alluminio. Tessuto: poliestere. Dimensioni: circa 51 x 47 x 96 cm (dimensioni quando ripiegato: circa 51 x 12 x 96 cm). Ideale per le sedie pieghevoli Campart Travel CH0592 e CH0596.</p><p> Dimenzioni per Poggiapiedi Pieghevole Campart Travel: </br><ul><li>Altezza: 13 Cm</li><li>Larghezza: 53 Cm</li><li>Profondita': 97 Cm</li><li>Peso: 1.377 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005939</p>","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc.</br><a href=""#maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""> Maggiori Informazioni</a>",1.377,1,"Taxable Goods","Catalog, Search",46.6,,,,Poggiapiedi-Pieghevole-Campart-Travel-CH0593 Blu Marino,"Poggiapiedi Pieghevole Campart Travel CH0593 Blu Marino","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0593 Blu Marino,CH0593 Blu Marino,","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc",http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_00.jpg,,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_00.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005939",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_01.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_02.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_08.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_07.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_06.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_05.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_04.jpg","GTIN=8713016005939",,,,,,,,,, +BB-F1520328,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Poggiapiedi Pieghevole Campart Travel CH0597 Grigio","<a id=""maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""><p>Approfitta</a> di un'esperienza rilassante con l'aiuto del <strong>poggiapiedi pieghevole Campart Travel</strong>! Questo<strong> poggiapiedi imbottito</strong> è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc. Possiede 2 ganci di circa 3 cm di diametro che possono essere facilmente attaccate alla barra inferiore delle sedie (utilizzabile solo per sedie con una barra inferiore di circa 2 cm di diametro). Struttura in alluminio. Tessuto: poliestere. Dimensioni: circa 51 x 47 x 96 cm (dimensioni quando ripiegato: circa 51 x 12 x 96 cm). Ideale per le sedie pieghevoli Campart Travel CH0592 e CH0596.</p><p> Dimenzioni per Poggiapiedi Pieghevole Campart Travel: </br><ul><li>Altezza: 13 Cm</li><li>Larghezza: 53 Cm</li><li>Profondita': 97 Cm</li><li>Peso: 1.377 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005977</p>","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc.</br><a href=""#maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""> Maggiori Informazioni</a>",1.377,1,"Taxable Goods","Catalog, Search",46.6,,,,Poggiapiedi-Pieghevole-Campart-Travel-CH0597 Grigio,"Poggiapiedi Pieghevole Campart Travel CH0597 Grigio","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0597 Grigio,CH0597 Grigio,","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc",http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_01.jpg,,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_01.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005977",7,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_00.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_02.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_08.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_07.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_06.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_05.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_04.jpg","GTIN=8713016005977",,,,,,,,,, +BB-H4502058,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Orologi da parete e da tavolo",base,"Orologio da Parete Star Wars","<a id=""maggiorni_informazioni"" title=""Orologio da Parete Star Wars""><p>I</a> fan di Star Wars non potranno fare a meno di appendere l'<strong>orologio da parete Star Wars</strong> in casa loro! Realizzato in plastica. Funziona a batterie (1 x AA, non incluse). Diametro circa: 25,5 cm. Spessore circa: 3,5 cm.</p><p> Dimenzioni per Orologio da Parete Star Wars: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 26 Cm</li><li>Profondita': 3.8 Cm</li><li>Peso: 0.287 Kg</li></ul></p><p>Codice Prodotto (EAN): 6950687214204</p>","I fan di Star Wars non potranno fare a meno di appendere l'orologio da parete Star Wars in casa loro! Realizzato in plastica.</br><a href=""#maggiorni_informazioni"" title=""Orologio da Parete Star Wars""> Maggiori Informazioni</a>",0.287,1,"Taxable Goods","Catalog, Search",22.5,,,,Orologio-da-Parete-Star-Wars,"Orologio da Parete Star Wars","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Orologi da parete e da tavolo,Orologi,parete,tavolo,","I fan di Star Wars non potranno fare a meno di appendere l'orologio da parete Star Wars in casa loro! Realizzato in plastica",http://dropshipping.bigbuy.eu/imgs/H4502058_84712.jpg,,http://dropshipping.bigbuy.eu/imgs/H4502058_84712.jpg,,,,,,"2016-08-08 21:09:24",,,,,,,,,,,,,,,,,"GTIN=6950687214204",130,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/H4502058_84713.jpg,http://dropshipping.bigbuy.eu/imgs/H4502058_84711.jpg,http://dropshipping.bigbuy.eu/imgs/H4502058_84710.jpg","GTIN=6950687214204",,,,,,,,,, +BB-G0500195,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Illuminazione LED",base,"Braccialetto Sportivo a LED MegaLed Rosso","<a id=""maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""><p>Se</a> ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed. Con questo<strong> braccialetto di sicurezza</strong> sarai visibile ai motorini e alle auto nell'oscurità, così da poter stare molto più sicuro e tranquillo. È dotato di 2 luci a LED con 2 possibili soluzioni (luce fissa ed intermittente). La lunghezza massima è di circa 55 cm e quella minima è di circa 42 cm. Molto leggero (circa 50 g). Autonomia circa: 24-40 ore. Funziona a batterie (2 x CR2023, incluse).</p><p> </p><p> Dimenzioni per Braccialetto Sportivo a LED MegaLed: </br><ul><li>Altezza: 3 Cm</li><li>Larghezza: 20 Cm</li><li>Profondita': 4 Cm</li><li>Peso: 0.049 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436545443507</p>","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed.</br><a href=""#maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""> Maggiori Informazioni</a>",0.049,1,"Taxable Goods","Catalog, Search",22,,,,Braccialetto-Sportivo-a-LED-MegaLed-Rosso,"Braccialetto Sportivo a LED MegaLed Rosso","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Illuminazione LED,Illuminazione,LED,Colore Rosso,Rosso,","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed",http://dropshipping.bigbuy.eu/imgs/G0500194_87774.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500194_87774.jpg,,,,,,"2016-01-08 12:34:41",,,,,,,,,,,,,,,,,"GTIN=8436545443507",22,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500194_87775.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87773.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87772.jpg","GTIN=8436545443507",,,,,,,,,, +BB-G0500196,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Illuminazione LED",base,"Braccialetto Sportivo a LED MegaLed Verde","<a id=""maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""><p>Se</a> ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed. Con questo<strong> braccialetto di sicurezza</strong> sarai visibile ai motorini e alle auto nell'oscurità, così da poter stare molto più sicuro e tranquillo. È dotato di 2 luci a LED con 2 possibili soluzioni (luce fissa ed intermittente). La lunghezza massima è di circa 55 cm e quella minima è di circa 42 cm. Molto leggero (circa 50 g). Autonomia circa: 24-40 ore. Funziona a batterie (2 x CR2023, incluse).</p><p> </p><p> Dimenzioni per Braccialetto Sportivo a LED MegaLed: </br><ul><li>Altezza: 3 Cm</li><li>Larghezza: 20 Cm</li><li>Profondita': 4 Cm</li><li>Peso: 0.049 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436545443507</p>","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed.</br><a href=""#maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""> Maggiori Informazioni</a>",0.049,1,"Taxable Goods","Catalog, Search",22,,,,Braccialetto-Sportivo-a-LED-MegaLed-Verde,"Braccialetto Sportivo a LED MegaLed Verde","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Illuminazione LED,Illuminazione,LED,Colore Verde,Verde,","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed",http://dropshipping.bigbuy.eu/imgs/G0500194_87775.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500194_87775.jpg,,,,,,"2016-01-08 12:34:41",,,,,,,,,,,,,,,,,"GTIN=8436545443507",37,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500194_87774.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87773.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87772.jpg","GTIN=8436545443507",,,,,,,,,, +BB-I2500333,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Orologi da parete e da tavolo",base,"Orologio da Parete Mom's Diner","<a id=""maggiorni_informazioni"" title=""Orologio da Parete Mom's Diner""><p>Decora</a> la tua cucina con l'originale <strong>orologio da parete</strong> <strong>Mom's Diner</strong> in stile vintage! È realizzato in legno. Diametro: 58 cm circa. Spessore: 0,8 cm circa. Funziona a pile (1 x AA, non inclusa).</p><p> Dimenzioni per Orologio da Parete Mom's Diner: </br><ul><li>Altezza: 59 Cm</li><li>Larghezza: 59 Cm</li><li>Profondita': 6 Cm</li><li>Peso: 2.1 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811345052</p>","Decora la tua cucina con l'originale orologio da parete Mom's Diner in stile vintage! È realizzato in legno.</br><a href=""#maggiorni_informazioni"" title=""Orologio da Parete Mom's Diner""> Maggiori Informazioni</a>",2.1,1,"Taxable Goods","Catalog, Search",42.5,,,,Orologio-da-Parete-Mom's-Diner,"Orologio da Parete Mom's Diner","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Orologi da parete e da tavolo,Orologi,parete,tavolo,","Decora la tua cucina con l'originale orologio da parete Mom's Diner in stile vintage! È realizzato in legno",http://dropshipping.bigbuy.eu/imgs/I2500333_88061.jpg,,http://dropshipping.bigbuy.eu/imgs/I2500333_88061.jpg,,,,,,"2016-07-21 13:05:12",,,,,,,,,,,,,,,,,"GTIN=4029811345052",2,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I2500333_88060.jpg,http://dropshipping.bigbuy.eu/imgs/I2500333_88059.jpg,http://dropshipping.bigbuy.eu/imgs/I2500333_88058.jpg","GTIN=4029811345052",,,,,,,,,, +BB-I2500334,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Orologi da parete e da tavolo",base,"Orologio da Parete Coffee Endless Cup","<a id=""maggiorni_informazioni"" title=""Orologio da Parete Coffee Endless Cup""><p>Se</a> sei un appassionato di caffè, non puoi rimanere senza l'<strong>orologio da parete Coffee Endless Cup</strong>! Un orologio vintage in legno con un design in perfetto stile caffettoso! Diametro: 58 cm circa. Spessore: 0,8 cm circa. Funziona a pile (1 x AA, non inclusa).</p><p> Dimenzioni per Orologio da Parete Coffee Endless Cup: </br><ul><li>Altezza: 59 Cm</li><li>Larghezza: 59 Cm</li><li>Profondita': 6 Cm</li><li>Peso: 2.1 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811345069</p>","Se sei un appassionato di caffè, non puoi rimanere senza l'orologio da parete Coffee Endless Cup! Un orologio vintage in legno con un design in perfetto stile caffettoso! Diametro: 58 cm circa.</br><a href=""#maggiorni_informazioni"" title=""Orologio da Parete Coffee Endless Cup""> Maggiori Informazioni</a>",2.1,1,"Taxable Goods","Catalog, Search",42.5,,,,Orologio-da-Parete-Coffee-Endless-Cup,"Orologio da Parete Coffee Endless Cup","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Orologi da parete e da tavolo,Orologi,parete,tavolo,","Se sei un appassionato di caffè, non puoi rimanere senza l'orologio da parete Coffee Endless Cup! Un orologio vintage in legno con un design in perfetto stile caffettoso! Diametro: 58 cm circa",http://dropshipping.bigbuy.eu/imgs/I2500334_88065.jpg,,http://dropshipping.bigbuy.eu/imgs/I2500334_88065.jpg,,,,,,"2016-08-30 13:41:52",,,,,,,,,,,,,,,,,"GTIN=4029811345069",4,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I2500334_88064.jpg,http://dropshipping.bigbuy.eu/imgs/I2500334_88063.jpg,http://dropshipping.bigbuy.eu/imgs/I2500334_88062.jpg","GTIN=4029811345069",,,,,,,,,, +BB-V0000252,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Insegna Dito Vintage Look Stop!","<a id=""maggiorni_informazioni"" title=""Insegna Dito Vintage Look""><p>Se</a> sei alla ricerca di una <strong>decorazione vintage</strong> originale e divertente per la tua casa, l'<strong>insegna dito Vintage Look</strong> ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno. Dimensioni: 69 x 17 x 1 cm circa.</p><p> Dimenzioni per Insegna Dito Vintage Look: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 69 Cm</li><li>Profondita': 1 Cm</li><li>Peso: 0.63 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346196</p>","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno.</br><a href=""#maggiorni_informazioni"" title=""Insegna Dito Vintage Look""> Maggiori Informazioni</a>",0.63,1,"Taxable Goods","Catalog, Search",15.99,,,,Insegna-Dito-Vintage-Look-Stop!,"Insegna Dito Vintage Look Stop!","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Stop!,Stop!,","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno",http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,,,,,,"2016-02-29 09:49:10",,,,,,,,,,,,,,,,,"GTIN=4029811346196",21,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89743.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89742.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89741.jpg","GTIN=4029811346196",,,,,,,,,, +BB-V0000253,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Insegna Dito Vintage Look Adults Only","<a id=""maggiorni_informazioni"" title=""Insegna Dito Vintage Look""><p>Se</a> sei alla ricerca di una <strong>decorazione vintage</strong> originale e divertente per la tua casa, l'<strong>insegna dito Vintage Look</strong> ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno. Dimensioni: 69 x 17 x 1 cm circa.</p><p> Dimenzioni per Insegna Dito Vintage Look: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 69 Cm</li><li>Profondita': 1 Cm</li><li>Peso: 0.63 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346202</p>","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno.</br><a href=""#maggiorni_informazioni"" title=""Insegna Dito Vintage Look""> Maggiori Informazioni</a>",0.63,1,"Taxable Goods","Catalog, Search",15.99,,,,Insegna-Dito-Vintage-Look-Adults Only,"Insegna Dito Vintage Look Adults Only","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Adults Only,Adults Only,","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno",http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,,,,,,"2016-02-29 09:49:10",,,,,,,,,,,,,,,,,"GTIN=4029811346202",18,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89743.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89742.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89741.jpg","GTIN=4029811346202",,,,,,,,,, +BB-V0000254,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Insegna Dito Vintage Look Talk","<a id=""maggiorni_informazioni"" title=""Insegna Dito Vintage Look""><p>Se</a> sei alla ricerca di una <strong>decorazione vintage</strong> originale e divertente per la tua casa, l'<strong>insegna dito Vintage Look</strong> ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno. Dimensioni: 69 x 17 x 1 cm circa.</p><p> Dimenzioni per Insegna Dito Vintage Look: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 69 Cm</li><li>Profondita': 1 Cm</li><li>Peso: 0.63 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346318</p>","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno.</br><a href=""#maggiorni_informazioni"" title=""Insegna Dito Vintage Look""> Maggiori Informazioni</a>",0.63,1,"Taxable Goods","Catalog, Search",15.99,,,,Insegna-Dito-Vintage-Look-Talk,"Insegna Dito Vintage Look Talk","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Talk,Talk,","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno",http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,,,,,,"2016-02-29 09:49:10",,,,,,,,,,,,,,,,,"GTIN=4029811346318",8,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89743.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89742.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89741.jpg","GTIN=4029811346318",,,,,,,,,, +BB-V0000256,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Freccia Decorativa Vintage Look Go Left","<a id=""maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""><p>Stupisci</a> tutti con la divertente ed originale <strong>freccia decorativa Vintage Look</strong>! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno. Misure appross.: 25 x 40 x 1 cm.</p><p> Dimenzioni per Freccia Decorativa Vintage Look: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 0.8 Cm</li><li>Profondita': 40 Cm</li><li>Peso: 0.376 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346325</p>","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno.</br><a href=""#maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""> Maggiori Informazioni</a>",0.376,1,"Taxable Goods","Catalog, Search",13.9,,,,Freccia-Decorativa-Vintage-Look-Go Left,"Freccia Decorativa Vintage Look Go Left","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Go Left,Go Left,","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno",http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,,,,,,"2016-02-29 10:39:59",,,,,,,,,,,,,,,,,"GTIN=4029811346325",4,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89759.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89758.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89757.jpg","GTIN=4029811346325",,,,,,,,,, +BB-V0000257,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Freccia Decorativa Vintage Look Exit","<a id=""maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""><p>Stupisci</a> tutti con la divertente ed originale <strong>freccia decorativa Vintage Look</strong>! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno. Misure appross.: 25 x 40 x 1 cm.</p><p> Dimenzioni per Freccia Decorativa Vintage Look: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 0.8 Cm</li><li>Profondita': 40 Cm</li><li>Peso: 0.376 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346332</p>","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno.</br><a href=""#maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""> Maggiori Informazioni</a>",0.376,1,"Taxable Goods","Catalog, Search",13.9,,,,Freccia-Decorativa-Vintage-Look-Exit,"Freccia Decorativa Vintage Look Exit","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Exit,Exit,","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno",http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,,,,,,"2016-02-29 10:39:59",,,,,,,,,,,,,,,,,"GTIN=4029811346332",19,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89759.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89758.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89757.jpg","GTIN=4029811346332",,,,,,,,,, +BB-V0000258,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Freccia Decorativa Vintage Look Cold beer here","<a id=""maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""><p>Stupisci</a> tutti con la divertente ed originale <strong>freccia decorativa Vintage Look</strong>! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno. Misure appross.: 25 x 40 x 1 cm.</p><p> Dimenzioni per Freccia Decorativa Vintage Look: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 0.8 Cm</li><li>Profondita': 40 Cm</li><li>Peso: 0.376 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346349</p>","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno.</br><a href=""#maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""> Maggiori Informazioni</a>",0.376,1,"Taxable Goods","Catalog, Search",13.9,,,,Freccia-Decorativa-Vintage-Look-Cold beer here,"Freccia Decorativa Vintage Look Cold beer here","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Cold beer here,Cold beer here,","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno",http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,,,,,,"2016-02-29 10:39:59",,,,,,,,,,,,,,,,,"GTIN=4029811346349",20,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89759.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89758.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89757.jpg","GTIN=4029811346349",,,,,,,,,, +BB-V0200190,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Centrotavola e Vasi",base,"Ciotola in Bambù TakeTokio Bianco","<a id=""maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""><p>Arricchisci</a> la selezione dei tuoi <strong>utensili da cucina</strong> con la <strong>ciotola in bambù</strong> <strong>TakeTokio</strong>, una <strong>ciotola da cucina</strong> funzionale e dal design moderno è perfetta come <strong>insalatiera</strong>, portafrutta, ecc. Realizzata in pregiato legno di bambù. Dimensioni (diametro x altezza): 25 x 15 cm circa. Diametro della base: 9 cm circa.</p><p><a href=""http://www.taketokio.com/"" target=""_blank""><strong>www.taketokio.com</strong></a></p><p> Dimenzioni per Ciotola in Bambù TakeTokio: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 15 Cm</li><li>Peso: 0.39 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158904577</p>","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc.</br><a href=""#maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""> Maggiori Informazioni</a>",0.39,1,"Taxable Goods","Catalog, Search",19.8,,,,Ciotola-in-Bambù-TakeTokio-Bianco,"Ciotola in Bambù TakeTokio Bianco","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Centrotavola e Vasi,Centrotavola,Vasi,Colore Bianco,Bianco,","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc",http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8718158904577",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90744.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90743.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90742.jpg","GTIN=8718158904577",,,,,,,,,, +BB-V0200192,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Centrotavola e Vasi",base,"Ciotola in Bambù TakeTokio Grigio","<a id=""maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""><p>Arricchisci</a> la selezione dei tuoi <strong>utensili da cucina</strong> con la <strong>ciotola in bambù</strong> <strong>TakeTokio</strong>, una <strong>ciotola da cucina</strong> funzionale e dal design moderno è perfetta come <strong>insalatiera</strong>, portafrutta, ecc. Realizzata in pregiato legno di bambù. Dimensioni (diametro x altezza): 25 x 15 cm circa. Diametro della base: 9 cm circa.</p><p><a href=""http://www.taketokio.com/"" target=""_blank""><strong>www.taketokio.com</strong></a></p><p> Dimenzioni per Ciotola in Bambù TakeTokio: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 15 Cm</li><li>Peso: 0.39 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158904577</p>","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc.</br><a href=""#maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""> Maggiori Informazioni</a>",0.39,1,"Taxable Goods","Catalog, Search",19.8,,,,Ciotola-in-Bambù-TakeTokio-Grigio,"Ciotola in Bambù TakeTokio Grigio","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Centrotavola e Vasi,Centrotavola,Vasi,Colore Grigio,Grigio,","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc",http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8718158904577",26,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90744.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90743.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90742.jpg","GTIN=8718158904577",,,,,,,,,, +BB-V0200191,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Centrotavola e Vasi",base,"Ciotola in Bambù TakeTokio Nero","<a id=""maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""><p>Arricchisci</a> la selezione dei tuoi <strong>utensili da cucina</strong> con la <strong>ciotola in bambù</strong> <strong>TakeTokio</strong>, una <strong>ciotola da cucina</strong> funzionale e dal design moderno è perfetta come <strong>insalatiera</strong>, portafrutta, ecc. Realizzata in pregiato legno di bambù. Dimensioni (diametro x altezza): 25 x 15 cm circa. Diametro della base: 9 cm circa.</p><p><a href=""http://www.taketokio.com/"" target=""_blank""><strong>www.taketokio.com</strong></a></p><p> Dimenzioni per Ciotola in Bambù TakeTokio: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 15 Cm</li><li>Peso: 0.39 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158904577</p>","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc.</br><a href=""#maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""> Maggiori Informazioni</a>",0.39,1,"Taxable Goods","Catalog, Search",19.8,,,,Ciotola-in-Bambù-TakeTokio-Nero,"Ciotola in Bambù TakeTokio Nero","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Centrotavola e Vasi,Centrotavola,Vasi,Colore Nero,Nero,","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc",http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8718158904577",22,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90744.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90743.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90742.jpg","GTIN=8718158904577",,,,,,,,,, +BB-V0200360,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Scatola porta Tè Flower Vintage Coconut Rosa","<a id=""maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""><p>Gli</a> amanti della moda vintage non potranno resistere di fronte all'adorabile <strong>scatola porta tè Flower Vintage Coconut</strong>! Una <strong>scatola vintage</strong> in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi. Dispone di un coperchio in cristallo e vari scompartimenti. Dimensioni: circa 23 x 7 x 23 cm.</p><p><a href=""http://www.vintagecoconut.com"" target=""_blank""><strong>www.vintagecoconut.com</strong></a></p><p> Dimenzioni per Scatola porta Tè Flower Vintage Coconut: </br><ul><li>Altezza: 23 Cm</li><li>Larghezza: 7.1 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.795 Kg</li></ul></p><p>Codice Prodotto (EAN): 8711295889547</p>","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi.</br><a href=""#maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""> Maggiori Informazioni</a>",0.795,1,"Taxable Goods","Catalog, Search",25.9,,,,Scatola-porta-Tè-Flower-Vintage-Coconut-Rosa,"Scatola porta Tè Flower Vintage Coconut Rosa","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,Colore Rosa,Rosa,","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi",http://dropshipping.bigbuy.eu/imgs/V0200328_92649.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200328_92649.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8711295889547",13,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200328_92648.jpg,http://dropshipping.bigbuy.eu/imgs/V0200328_92647.jpg","GTIN=8711295889547",,,,,,,,,, +BB-V0200361,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Scatola porta Tè Flower Vintage Coconut Azzurro","<a id=""maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""><p>Gli</a> amanti della moda vintage non potranno resistere di fronte all'adorabile <strong>scatola porta tè Flower Vintage Coconut</strong>! Una <strong>scatola vintage</strong> in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi. Dispone di un coperchio in cristallo e vari scompartimenti. Dimensioni: circa 23 x 7 x 23 cm.</p><p><a href=""http://www.vintagecoconut.com"" target=""_blank""><strong>www.vintagecoconut.com</strong></a></p><p> Dimenzioni per Scatola porta Tè Flower Vintage Coconut: </br><ul><li>Altezza: 23 Cm</li><li>Larghezza: 7.1 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.795 Kg</li></ul></p><p>Codice Prodotto (EAN): 8711295889547</p>","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi.</br><a href=""#maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""> Maggiori Informazioni</a>",0.795,1,"Taxable Goods","Catalog, Search",25.9,,,,Scatola-porta-Tè-Flower-Vintage-Coconut-Azzurro,"Scatola porta Tè Flower Vintage Coconut Azzurro","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,Colore Azzurro,Azzurro,","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi",http://dropshipping.bigbuy.eu/imgs/V0200328_92648.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200328_92648.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8711295889547",21,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200328_92649.jpg,http://dropshipping.bigbuy.eu/imgs/V0200328_92647.jpg","GTIN=8711295889547",,,,,,,,,, +BB-V0200353,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Barbecue",base,"Ventilatore a Pistola classico per Barbecue BBQ Classics","<a id=""maggiorni_informazioni"" title=""Ventilatore a Pistola classico per Barbecue BBQ Classics""><p>Utilizza</a> i migliori barbecue alimentando il fuoco con il <strong>ventilatore a pistola classico per babecue BBQ Classics</strong>! Basterà solo fare una leggera pressione sul pulsante per far uscire l'aria.</p><p><a href=""http://www.bbqclassics.com"" target=""_blank""><strong>www.bbqclassics.com</strong></a></p><ul><li>Realizzato in plastica e metallo</li><li>Dimensioni: 25 x 18 x 4 cm circa</li></ul><p> Dimenzioni per Ventilatore a Pistola classico per Barbecue BBQ Classics: </br><ul><li>Altezza: 5.5 Cm</li><li>Larghezza: 11 Cm</li><li>Profondita': 22 Cm</li><li>Peso: 0.167 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158032706</p>","Utilizza i migliori barbecue alimentando il fuoco con il ventilatore a pistola classico per babecue BBQ Classics! Basterà solo fare una leggera pressione sul pulsante per far uscire l'aria.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore a Pistola classico per Barbecue BBQ Classics""> Maggiori Informazioni</a>",0.167,1,"Taxable Goods","Catalog, Search",9.3,,,,Ventilatore-a-Pistola-classico-per-Barbecue-BBQ-Classics,"Ventilatore a Pistola classico per Barbecue BBQ Classics","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Barbecue,","Utilizza i migliori barbecue alimentando il fuoco con il ventilatore a pistola classico per babecue BBQ Classics! Basterà solo fare una leggera pressione sul pulsante per far uscire l'aria",http://dropshipping.bigbuy.eu/imgs/V0200353_92695.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200353_92695.jpg,,,,,,"2016-09-13 09:56:07",,,,,,,,,,,,,,,,,"GTIN=8718158032706",60,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200353_92696.jpg,http://dropshipping.bigbuy.eu/imgs/V0200353_92694.jpg","GTIN=8718158032706",,,,,,,,,, +BB-V1600123,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Frozen (32 x 23 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (32 x 23 cm)""><p>Non</a> c'è nulla di meglio che tenere le camere dei più piccini in ordine in modo originale e divertente. Con il <strong>contenitore portagiochi Frozen (32 x 23 cm)</strong> sarà semplicissimo!</p><ul><li>Realizzato in polipropilene</li><li>Dimensioni aprossimative: 32 x 15 x 23 cm</li><li>Età consigliata: +3 anni</li></ul><p> </p><p> Dimenzioni per Contenitore Portagiochi Frozen (32 x 23 cm): </br><ul><li>Altezza: 15 Cm</li><li>Larghezza: 34 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.331 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766006</p>","Non c'è nulla di meglio che tenere le camere dei più piccini in ordine in modo originale e divertente.</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (32 x 23 cm)""> Maggiori Informazioni</a>",0.331,1,"Taxable Goods","Catalog, Search",21.9,,,,Contenitore-Portagiochi-Frozen-(32-x-23-cm),"Contenitore Portagiochi Frozen (32 x 23 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","Non c'è nulla di meglio che tenere le camere dei più piccini in ordine in modo originale e divertente",http://dropshipping.bigbuy.eu/imgs/V1600123_93002.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600123_93002.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8412842766006",48,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600123_93005.jpg,http://dropshipping.bigbuy.eu/imgs/V1600123_93004.jpg,http://dropshipping.bigbuy.eu/imgs/V1600123_93003.jpg","GTIN=8412842766006",,,,,,,,,, +BB-V1600124,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Spiderman (32 x 23 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (32 x 23 cm)""><p>Desideri</a> sorprendere i più piccini con un regalo molto originale? Il <strong>contenitore portagiochi Spiderman (32 x 23 cm)</strong> decorerà e metterà in ordine le loro camerette.</p><ul><li>Realizzato in polipropilene</li><li>Dimensioni approssimative: 32 x 15 x 23 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Contenitore Portagiochi Spiderman (32 x 23 cm): </br><ul><li>Altezza: 15 Cm</li><li>Larghezza: 34 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.331 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766037</p>","Desideri sorprendere i più piccini con un regalo molto originale? Il contenitore portagiochi Spiderman (32 x 23 cm) decorerà e metterà in ordine le loro camerette.</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (32 x 23 cm)""> Maggiori Informazioni</a>",0.331,1,"Taxable Goods","Catalog, Search",21.9,,,,Contenitore-Portagiochi-Spiderman--(32-x-23-cm),"Contenitore Portagiochi Spiderman (32 x 23 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","Desideri sorprendere i più piccini con un regalo molto originale? Il contenitore portagiochi Spiderman (32 x 23 cm) decorerà e metterà in ordine le loro camerette",http://dropshipping.bigbuy.eu/imgs/V1600124_93006.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600124_93006.jpg,,,,,,"2016-08-08 21:09:24",,,,,,,,,,,,,,,,,"GTIN=8412842766037",52,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600124_93008.jpg,http://dropshipping.bigbuy.eu/imgs/V1600124_93007.jpg","GTIN=8412842766037",,,,,,,,,, +BB-V1600125,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Frozen (45 x 32 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (45 x 32 cm)""><p>Insegna</a> ai tuoi bambini a tenere i giocattoli conservati ben in ordine con l'aiuto del <strong>contenitore portagiochi Frozen (45 x 32 cm)</strong>. Il <strong>portagiocattoli</strong> che tutte le bambine sognano!</p><ul><li>Realizzato in polipropilene</li><li>Dimensioni: circa 45 x 22 x 32 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Contenitore Portagiochi Frozen (45 x 32 cm): </br><ul><li>Altezza: 22 Cm</li><li>Larghezza: 37 Cm</li><li>Profondita': 45 Cm</li><li>Peso: 0.775 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766129</p>","Insegna ai tuoi bambini a tenere i giocattoli conservati ben in ordine con l'aiuto del contenitore portagiochi Frozen (45 x 32 cm).</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (45 x 32 cm)""> Maggiori Informazioni</a>",0.775,1,"Taxable Goods","Catalog, Search",39.6,,,,Contenitore-Portagiochi-Frozen-(45-x-32-cm),"Contenitore Portagiochi Frozen (45 x 32 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","Insegna ai tuoi bambini a tenere i giocattoli conservati ben in ordine con l'aiuto del contenitore portagiochi Frozen (45 x 32 cm)",http://dropshipping.bigbuy.eu/imgs/V1600125_93010.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600125_93010.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8412842766129",17,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600125_93011.jpg,http://dropshipping.bigbuy.eu/imgs/V1600125_93009.jpg","GTIN=8412842766129",,,,,,,,,, +BB-V1600126,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Spiderman (45 x 32 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (45 x 32 cm)""><p>I</a> piccoli di casa ora possono ordinare e riporre i loro giocattoli facilmente grazie al <strong>c<strong>ontenitore <strong>portagiochi</strong></strong> Spiderman</strong><strong> (45 x 32 cm)</strong>. Il <strong>c<strong>ontenitore <strong>portagiochi</strong></strong> </strong>preferito dai bambini!</p><ul><li>Fabbricato in polipropilene</li><li>Dimensioni: circa 45 x 22 x 32 cm</li><li>Età raccomandata: +3 anni</li></ul><p> Dimenzioni per Contenitore Portagiochi Spiderman (45 x 32 cm): </br><ul><li>Altezza: 22 Cm</li><li>Larghezza: 37 Cm</li><li>Profondita': 45 Cm</li><li>Peso: 0.775 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766150</p>","I piccoli di casa ora possono ordinare e riporre i loro giocattoli facilmente grazie al contenitore portagiochi Spiderman (45 x 32 cm).</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (45 x 32 cm)""> Maggiori Informazioni</a>",0.775,1,"Taxable Goods","Catalog, Search",39.6,,,,Contenitore-Portagiochi-Spiderman-(45-x-32-cm),"Contenitore Portagiochi Spiderman (45 x 32 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","I piccoli di casa ora possono ordinare e riporre i loro giocattoli facilmente grazie al contenitore portagiochi Spiderman (45 x 32 cm)",http://dropshipping.bigbuy.eu/imgs/V1600126_93012.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600126_93012.jpg,,,,,,"2016-08-08 21:09:24",,,,,,,,,,,,,,,,,"GTIN=8412842766150",18,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600126_93014.jpg,http://dropshipping.bigbuy.eu/imgs/V1600126_93013.jpg","GTIN=8412842766150",,,,,,,,,, +BB-V1300154,,Default,simple,"Default Category/Relax Tempo Libero,Default Category/Relax Tempo Libero/Mare e Piscina",base,"Zaino per Piscina Spiderman (4 pezzi)","<a id=""maggiorni_informazioni"" title=""Zaino per Piscina Spiderman (4 pezzi)""><p>Vorresti</a> sorprendere i più piccoli della casa con un <strong>regalo originale</strong>? Se adorano il mare o la piscina, lo <strong>zaino per piscina Spiderman (4 pezzi)</strong> li farà impazzire.</p><ul><li>Dispone di una cerniera, una rete posteriore e uno scompartimento per il nome</li><li>Dispone di manico regolabile</li><li>1 asciugamano 42,5 x 90,5 cm (80 % poliestere e 20 % poliammide) dall'asciugatura rapida</li><li>1 cuffia da nuoto taglia unica (85 % poliestere e 15 % elastam)</li><li>1 paio di occhialini da nuoto (norme 89/686/CEE e ISO 12312-1:2013) anti appannamento</li><li>Dimensioni dello zaino: 24 x 31 x 6 cm circa</li></ul><p> Dimenzioni per Zaino per Piscina Spiderman (4 pezzi): </br><ul><li>Altezza: 3 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 30.5 Cm</li><li>Peso: 0.283 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000752232</p>","Vorresti sorprendere i più piccoli della casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Spiderman (4 pezzi) li farà impazzire.</br><a href=""#maggiorni_informazioni"" title=""Zaino per Piscina Spiderman (4 pezzi)""> Maggiori Informazioni</a>",0.283,1,"Taxable Goods","Catalog, Search",37.9,,,,Zaino-per-Piscina-Spiderman-(4-pezzi),"Zaino per Piscina Spiderman (4 pezzi)","Relax Tempo Libero,Relax,Tempo,Libero,Mare e Piscina,Mare,Piscina,","Vorresti sorprendere i più piccoli della casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Spiderman (4 pezzi) li farà impazzire",http://dropshipping.bigbuy.eu/imgs/V1300154_93570.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300154_93570.jpg,,,,,,"2016-08-16 13:42:17",,,,,,,,,,,,,,,,,"GTIN=7569000752232",129,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300154_93576.jpg,http://dropshipping.bigbuy.eu/imgs/V1300154_93575.jpg,http://dropshipping.bigbuy.eu/imgs/V1300154_93574.jpg,http://dropshipping.bigbuy.eu/imgs/V1300154_93573.jpg,http://dropshipping.bigbuy.eu/imgs/V1300154_93572.jpg,http://dropshipping.bigbuy.eu/imgs/V1300154_93571.jpg","GTIN=7569000752232",,,,,,,,,, +BB-V1300156,,Default,simple,"Default Category/Relax Tempo Libero,Default Category/Relax Tempo Libero/Mare e Piscina",base,"Zaino per Piscina Frozen (4 pezzi)","<a id=""maggiorni_informazioni"" title=""Zaino per Piscina Frozen (4 pezzi)""><p>Vuoi</a> fare un <strong>regalo originale</strong> ai piccoli di casa? Se adorano il mare o la piscina, lo <strong>zaino per piscina Frozen (4 pezzi)</strong> li farà impazzire.</p><ul><li>Presenta una cerniera, una rete posteriore e uno scompartimento per il nome</li><li>Dispone di manico regolabile</li><li>1 asciugamano 42,5 x 90,5 cm (80 % poliestere e 20 % poliammide) dall'asciugatura rapida</li><li>1 cuffia da nuoto taglia unica (85 % poliestere e 15 % elastam)</li><li>1 paio di occhialini da nuoto (norme 89/686/CEE e ISO 12312-1:2013) anti appannamento</li><li>Dimensioni dello zaino: circa 24 x 31 x 6 cm</li></ul><p> Dimenzioni per Zaino per Piscina Frozen (4 pezzi): </br><ul><li>Altezza: 2 Cm</li><li>Larghezza: 23 Cm</li><li>Profondita': 30.5 Cm</li><li>Peso: 0.277 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000752225</p>","Vuoi fare un regalo originale ai piccoli di casa? Se adorano il mare o la piscina, lo zaino per piscina Frozen (4 pezzi) li farà impazzire.</br><a href=""#maggiorni_informazioni"" title=""Zaino per Piscina Frozen (4 pezzi)""> Maggiori Informazioni</a>",0.277,1,"Taxable Goods","Catalog, Search",37.9,,,,Zaino-per-Piscina-Frozen-(4-pezzi),"Zaino per Piscina Frozen (4 pezzi)","Relax Tempo Libero,Relax,Tempo,Libero,Mare e Piscina,Mare,Piscina,","Vuoi fare un regalo originale ai piccoli di casa? Se adorano il mare o la piscina, lo zaino per piscina Frozen (4 pezzi) li farà impazzire",http://dropshipping.bigbuy.eu/imgs/V1300156_93580.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300156_93580.jpg,,,,,,"2016-08-26 13:31:11",,,,,,,,,,,,,,,,,"GTIN=7569000752225",104,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300156_93594.jpg,http://dropshipping.bigbuy.eu/imgs/V1300156_93585.jpg,http://dropshipping.bigbuy.eu/imgs/V1300156_93584.jpg,http://dropshipping.bigbuy.eu/imgs/V1300156_93583.jpg,http://dropshipping.bigbuy.eu/imgs/V1300156_93582.jpg,http://dropshipping.bigbuy.eu/imgs/V1300156_93581.jpg","GTIN=7569000752225",,,,,,,,,, +BB-V1300157,,Default,simple,"Default Category/Relax Tempo Libero,Default Category/Relax Tempo Libero/Mare e Piscina",base,"Zaino per Piscina Minnie (4 pezzi)","<a id=""maggiorni_informazioni"" title=""Zaino per Piscina Minnie (4 pezzi)""><p>Ti</a> piacerebbe sorprendere le bimbe della casa con un <strong>regalo originale</strong>? Se adorano il mare o la piscina, lo <strong>zaino per piscina Minnie (4 pezzi)</strong> le farà impazzire.</p><ul><li>Dispone di una cerniera, una rete posteriore e uno scompartimento per il nome</li><li>Dispone di manico regolabile</li><li>1 asciugamano 42,5 x 90,5 cm (80 % poliestere e 20 % poliammide) dall'asciugatura rapida</li><li>1 cuffia da nuoto taglia unica (85 % poliestere e 15 % elastam)</li><li>1 paio di occhialini da nuoto (norme 89/686/CEE e ISO 12312-1:2013) anti appannamento</li><li>Dimensioni dello zaino: 24 x 31 x 6 cm circa</li></ul><p> Dimenzioni per Zaino per Piscina Minnie (4 pezzi): </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 24 Cm</li><li>Profondita': 30 Cm</li><li>Peso: 0.277 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934823796</p>","Ti piacerebbe sorprendere le bimbe della casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Minnie (4 pezzi) le farà impazzire.</br><a href=""#maggiorni_informazioni"" title=""Zaino per Piscina Minnie (4 pezzi)""> Maggiori Informazioni</a>",0.277,1,"Taxable Goods","Catalog, Search",37.9,,,,Zaino-per-Piscina-Minnie-(4-pezzi),"Zaino per Piscina Minnie (4 pezzi)","Relax Tempo Libero,Relax,Tempo,Libero,Mare e Piscina,Mare,Piscina,","Ti piacerebbe sorprendere le bimbe della casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Minnie (4 pezzi) le farà impazzire",http://dropshipping.bigbuy.eu/imgs/V1300157_93587.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300157_93587.jpg,,,,,,"2016-08-26 13:30:29",,,,,,,,,,,,,,,,,"GTIN=8427934823796",137,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300157_93593.jpg,http://dropshipping.bigbuy.eu/imgs/V1300157_93592.jpg,http://dropshipping.bigbuy.eu/imgs/V1300157_93591.jpg,http://dropshipping.bigbuy.eu/imgs/V1300157_93590.jpg,http://dropshipping.bigbuy.eu/imgs/V1300157_93589.jpg,http://dropshipping.bigbuy.eu/imgs/V1300157_93586.jpg","GTIN=8427934823796",,,,,,,,,, +BB-V1300158,,Default,simple,"Default Category/Relax Tempo Libero,Default Category/Relax Tempo Libero/Mare e Piscina",base,"Zaino per Piscina Avengers (4 pezzi)","<a id=""maggiorni_informazioni"" title=""Zaino per Piscina Avengers (4 pezzi)""><p>Ti</a> piacerebbe sorprendere i più piccoli della casa con un <strong>regalo originale</strong>? Se adorano il mare o la piscina, lo <strong>zaino per piscina Avengers (4 pezzi)</strong> li farà impazzire.</p><ul><li>Dispone di una cerniera, una rete posteriore e uno scompartimento per il nome</li><li>Dispone di manico regolabile</li><li>1 asciugamano 42,5 x 90,5 cm (80 % poliestere e 20 % poliammide) dall'asciugatura rapida</li><li>1 cuffia da nuoto taglia unica (85 % poliestere e 15 % elastam)</li><li>1 paio di occhialini da nuoto (norme 89/686/CEE e ISO 12312-1:2013) anti appannamento</li><li>Dimensioni dello zaino: 24 x 31 x 6 cm circa</li></ul><p> Dimenzioni per Zaino per Piscina Avengers (4 pezzi): </br><ul><li>Altezza: 3 Cm</li><li>Larghezza: 23 Cm</li><li>Profondita': 31 Cm</li><li>Peso: 0.279 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000752249</p>","Ti piacerebbe sorprendere i più piccoli della casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Avengers (4 pezzi) li farà impazzire.</br><a href=""#maggiorni_informazioni"" title=""Zaino per Piscina Avengers (4 pezzi)""> Maggiori Informazioni</a>",0.279,1,"Taxable Goods","Catalog, Search",37.9,,,,Zaino-per-Piscina-Avengers-(4-pezzi),"Zaino per Piscina Avengers (4 pezzi)","Relax Tempo Libero,Relax,Tempo,Libero,Mare e Piscina,Mare,Piscina,","Ti piacerebbe sorprendere i più piccoli della casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Avengers (4 pezzi) li farà impazzire",http://dropshipping.bigbuy.eu/imgs/V1300158_93596.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300158_93596.jpg,,,,,,"2016-08-26 11:29:45",,,,,,,,,,,,,,,,,"GTIN=7569000752249",139,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300158_93601.jpg,http://dropshipping.bigbuy.eu/imgs/V1300158_93600.jpg,http://dropshipping.bigbuy.eu/imgs/V1300158_93599.jpg,http://dropshipping.bigbuy.eu/imgs/V1300158_93598.jpg,http://dropshipping.bigbuy.eu/imgs/V1300158_93597.jpg,http://dropshipping.bigbuy.eu/imgs/V1300158_93595.jpg","GTIN=7569000752249",,,,,,,,,, +BB-V1300159,,Default,simple,"Default Category/Relax Tempo Libero,Default Category/Relax Tempo Libero/Mare e Piscina",base,"Zaino per Piscina Minions (4 pezzi)","<a id=""maggiorni_informazioni"" title=""Zaino per Piscina Minions (4 pezzi)""><p>Ti</a> piacerebbe sorprendere i più piccoli di casa con un <strong>regalo originale</strong>? Se adorano il mare o la piscina, lo <strong>zaino per piscina Minions (4 pezzi)</strong> li farà impazzire.</p><ul><li>Dispone di una cerniera, una rete posteriore e uno scompartimento per il nome</li><li>Dispone di manico regolabile</li><li>1 asciugamano 42,5 x 90,5 cm (80 % poliestere e 20 % poliammide) dall'asciugatura rapida</li><li>1 cuffia da nuoto taglia unica (85 % poliestere e 15 % elastam)</li><li>1 paio di occhialini da nuoto (norme 89/686/CEE e ISO 12312-1:2013) anti appannamento</li><li>Dimensioni dello zaino: 24 x 31 x 6 cm circa</li></ul><p> Dimenzioni per Zaino per Piscina Minions (4 pezzi): </br><ul><li>Altezza: 3 Cm</li><li>Larghezza: 23 Cm</li><li>Profondita': 31 Cm</li><li>Peso: 0.279 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934823833</p>","Ti piacerebbe sorprendere i più piccoli di casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Minions (4 pezzi) li farà impazzire.</br><a href=""#maggiorni_informazioni"" title=""Zaino per Piscina Minions (4 pezzi)""> Maggiori Informazioni</a>",0.279,1,"Taxable Goods","Catalog, Search",37.9,,,,Zaino-per-Piscina-Minions-(4-pezzi),"Zaino per Piscina Minions (4 pezzi)","Relax Tempo Libero,Relax,Tempo,Libero,Mare e Piscina,Mare,Piscina,","Ti piacerebbe sorprendere i più piccoli di casa con un regalo originale? Se adorano il mare o la piscina, lo zaino per piscina Minions (4 pezzi) li farà impazzire",http://dropshipping.bigbuy.eu/imgs/V1300159_93602.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300159_93602.jpg,,,,,,"2016-08-26 11:29:10",,,,,,,,,,,,,,,,,"GTIN=8427934823833",132,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300159_93608.jpg,http://dropshipping.bigbuy.eu/imgs/V1300159_93607.jpg,http://dropshipping.bigbuy.eu/imgs/V1300159_93606.jpg,http://dropshipping.bigbuy.eu/imgs/V1300159_93605.jpg,http://dropshipping.bigbuy.eu/imgs/V1300159_93604.jpg,http://dropshipping.bigbuy.eu/imgs/V1300159_93603.jpg","GTIN=8427934823833",,,,,,,,,, +BB-G0500179,,Default,simple,"Default Category/Sport Fitness,Default Category/Sport Fitness/Abbigliamento, Accessori e Dispositivi Indossabili",base,"Clip di Sicurezza a LED per Scarpe da Corsa GoFit","<a id=""maggiorni_informazioni"" title=""Clip di Sicurezza a LED per Scarpe da Corsa GoFit ""><p>Ti</a> piace allenarti la sera all'aria aperta? Allora, non dimenticare di indossare la tua <strong>clip di sicurezza a LED per scarpe da corsa GoFit</strong>! Grazie a questo utile <strong>accessorio da corsa</strong>, sarai visibile al buio mentre corri o ti alleni. Include 2 tipi di luce (fissa o intermittente) e può essere facilmente applicata sulla parte posteriore della scarpa o indossata intorno al polso. Possiede un bottone on/off che ti permette inoltre di cambiare il tipo di luce.  <strong>Progettato in Europa</strong> con materiali di alta qualità. 1 pezzo incluso.</p><p>Caratteristiche: </p><ul><li>LED verde per alta visibilità</li><li>Circa 100 ore di luce intermittente</li><li>Circa 70 ore di luce fissa</li><li>Adattabile a scrarpe da 6 a 8,5 cm di larghezza</li><li>Funziona a batterie (2 x CR2032, incluse)</li></ul><p> Dimenzioni per Clip di Sicurezza a LED per Scarpe da Corsa GoFit : </br><ul><li>Altezza: 18 Cm</li><li>Larghezza: 9 Cm</li><li>Profondita': 3.7 Cm</li><li>Peso: 0.095 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417209116</p>","Ti piace allenarti la sera all'aria aperta? Allora, non dimenticare di indossare la tua clip di sicurezza a LED per scarpe da corsa GoFit! Grazie a questo utile accessorio da corsa, sarai visibile al buio mentre corri o ti alleni.</br><a href=""#maggiorni_informazioni"" title=""Clip di Sicurezza a LED per Scarpe da Corsa GoFit ""> Maggiori Informazioni</a>",0.095,1,"Taxable Goods","Catalog, Search",34.95,,,,Clip-di-Sicurezza-a-LED-per-Scarpe-da-Corsa-GoFit,"Clip di Sicurezza a LED per Scarpe da Corsa GoFit","Sport Fitness,Sport,Fitness,Abbigliamento, Accessori e Dispositivi Indossabili,Abbigliamento,,Accessori,Dispositivi,Indossabili,","Ti piace allenarti la sera all'aria aperta? Allora, non dimenticare di indossare la tua clip di sicurezza a LED per scarpe da corsa GoFit! Grazie a questo utile accessorio da corsa, sarai visibile al buio mentre corri o ti alleni",http://dropshipping.bigbuy.eu/imgs/clip_led_running_go_fit.jpg,,http://dropshipping.bigbuy.eu/imgs/clip_led_running_go_fit.jpg,,,,,,"2016-08-22 08:23:08",,,,,,,,,,,,,,,,,"GTIN=8018417209116",207,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/clip_led_running_go_fit_04.jpg,http://dropshipping.bigbuy.eu/imgs/clip_led_running_go_fit_02.jpg,http://dropshipping.bigbuy.eu/imgs/clip_led_running_go_fit_002.jpg","GTIN=8018417209116",,,,,,,,,, +BB-G0500187,,Default,simple,"Default Category/Sport Fitness,Default Category/Sport Fitness/Abbigliamento, Accessori e Dispositivi Indossabili",base,"Sensore di Velocità Bluetooth GoFit","<a id=""maggiorni_informazioni"" title=""Sensore di Velocità Bluetooth GoFit""><p>Se</a> sei un amante del ciclismo e vuoi tener traccia della velocità, ritmo e distanza mentre pedali? Allora non perdere l'occasione, acquista il <strong>sensore di velocità Bluetooth GoFit</strong>, in modo da essere in grado di monitorare tutti i suoi dati in ogni momento, grazie al suo ingegnoso dispositivo! Devi solo installare il sensore e scaricare l'apposita applicazione sul tuo telefono cellulare.<br /><br />Questo <strong>sensore di velocità e ritmo </strong>è stato progettato in Europa ed è costituito di materiali resistenti all'acqua, in polimeri termoplastici. Molto semplice da installare. Compatibile con iOS (7.0 e successivi) e Android (4.3 e successivi). Funziona a batterie (1 x CR2032, incluse).</p><p>Include:</p><ul><li>1 sensore di velocità e ritmo (dimensioni: circa 8,5 x 7 x 1,5 cm)</li><li>1 magnete per ritmo pedale (dimensioni: circa 1,5 x 3,5 x 2 cm)</li><li>1 magnete per ruote</li><li>1 cacciavite</li><li>2 fascette</li><li>1 banda elastica</li></ul><p> Dimenzioni per Sensore di Velocità Bluetooth GoFit: </br><ul><li>Altezza: 20.3 Cm</li><li>Larghezza: 9 Cm</li><li>Profondita': 3.5 Cm</li><li>Peso: 0.143 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417209109</p>","Se sei un amante del ciclismo e vuoi tener traccia della velocità, ritmo e distanza mentre pedali? Allora non perdere l'occasione, acquista il sensore di velocità Bluetooth GoFit, in modo da essere in grado di monitorare tutti i suoi dati in ogni momento, grazie al suo ingegnoso dispositivo! Devi solo installare il sensore e scaricare l'apposita applicazione sul tuo telefono cellulare.</br><a href=""#maggiorni_informazioni"" title=""Sensore di Velocità Bluetooth GoFit""> Maggiori Informazioni</a>",0.143,1,"Taxable Goods","Catalog, Search",179.9,,,,Sensore-di-Velocità-Bluetooth-GoFit,"Sensore di Velocità Bluetooth GoFit","Sport Fitness,Sport,Fitness,Abbigliamento, Accessori e Dispositivi Indossabili,Abbigliamento,,Accessori,Dispositivi,Indossabili,","Se sei un amante del ciclismo e vuoi tener traccia della velocità, ritmo e distanza mentre pedali? Allora non perdere l'occasione, acquista il sensore di velocità Bluetooth GoFit, in modo da essere in grado di monitorare tutti i suoi dati in ogni mo",http://dropshipping.bigbuy.eu/imgs/G0500187_81130.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500187_81130.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8018417209109",17,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500187_81089.jpg,http://dropshipping.bigbuy.eu/imgs/G0500187_81088.jpg,http://dropshipping.bigbuy.eu/imgs/G0500187_81087.jpg,http://dropshipping.bigbuy.eu/imgs/G0500187_81086.jpg","GTIN=8018417209109",,,,,,,,,, +BB-G0500188,,Default,simple,"Default Category/Sport Fitness,Default Category/Sport Fitness/Abbigliamento, Accessori e Dispositivi Indossabili",base,"Luce a Led di Sicurezza per Lacci GoFit (pacco da 2)","<a id=""maggiorni_informazioni"" title=""Luce a Led di Sicurezza per Lacci GoFit (pacco da 2)""><p>Da</a> ora in poi potrai fare jogging serenamente, sapendo che puoi essere visto dai veicoli intorno a te! <span id=""result_box"" lang=""en""><span class=""hps"">Basta inserire la <strong>luce a led di sicurezza </strong></span><strong>GoFit <span class=""hps atn"">(pacco da </span><span class=""hps"">2)</span> </strong>dentro ogni scarpa da corsa per aumentare la tua visibilità.<span class=""hps""> Le loro 2 potenti luci a LED verdi si attivano ad ogni passo che fai. È davvero semplice!</span><br /><br /><span class=""hps"">Ogni luce a LED funziona a pile</span> <span class=""hps atn"">(</span>2 x <span class=""hps"">CR1220</span>, 6 <span class=""hps"">V</span>, <span class=""hps"">incluse).</span> Durata delle batterie<span class=""hps"">: </span><span class=""hps"">150,000</span> <span class=""hps"">lampeggi di luce.</span> Queste luci a LED sono costituite di materiali di alta qualità e sono adatte all'utilizzo con i lacci con uno spessore massimo di 9 mm<span class=""hps"">.</span> <span class=""hps"">Include 2 unità. Dimensioni: circa</span> <span class=""hps"">4 x</span> <span class=""hps"">1,5</span> <span class=""hps"">x</span> <span class=""hps"">0,8</span> <span class=""hps"">cm.</span><br /></span></p><p> Dimenzioni per Luce a Led di Sicurezza per Lacci GoFit (pacco da 2): </br><ul><li>Altezza: 9 Cm</li><li>Larghezza: 3.7 Cm</li><li>Profondita': 20 Cm</li><li>Peso: 0.061 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417209482</p>","Da ora in poi potrai fare jogging serenamente, sapendo che puoi essere visto dai veicoli intorno a te! Basta inserire la luce a led di sicurezza GoFit (pacco da 2) dentro ogni scarpa da corsa per aumentare la tua visibilità.</br><a href=""#maggiorni_informazioni"" title=""Luce a Led di Sicurezza per Lacci GoFit (pacco da 2)""> Maggiori Informazioni</a>",0.061,1,"Taxable Goods","Catalog, Search",33.95,,,,Luce-a-Led-di-Sicurezza-per-Lacci-GoFit-(pacco-da-2),"Luce a Led di Sicurezza per Lacci GoFit (pacco da 2)","Sport Fitness,Sport,Fitness,Abbigliamento, Accessori e Dispositivi Indossabili,Abbigliamento,,Accessori,Dispositivi,Indossabili,","Da ora in poi potrai fare jogging serenamente, sapendo che puoi essere visto dai veicoli intorno a te! Basta inserire la luce a led di sicurezza GoFit (pacco da 2) dentro ogni scarpa da corsa per aumentare la tua visibilità",http://dropshipping.bigbuy.eu/imgs/G0500188_81129.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500188_81129.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8018417209482",238,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500188_81096.jpg,http://dropshipping.bigbuy.eu/imgs/G0500188_81095.jpg","GTIN=8018417209482",,,,,,,,,, +BB-G0500189,,Default,simple,"Default Category/Sport Fitness,Default Category/Sport Fitness/Abbigliamento, Accessori e Dispositivi Indossabili",base,"Bracciale di Sicurezza LED GoFit","<a id=""maggiorni_informazioni"" title=""Bracciale di Sicurezza LED GoFit""><p>Non</a> possiedi ancora il <strong>bracciale di sicurezza LED</strong> <strong>GoFit</strong>? Non perdertelo e pratica i tuoi sport preferiti con questo leggero e comodo <strong>bracciale </strong>progettato in Europa. Qualunque auto o moto ti vedrà nel buio! Include 2 luci a LED e 2 modalità di illuminazione (fissa e lampeggiante) che puoi scegliere premendo il pulsante del bracciale. La cinghia di velcro lo fissa al braccio ed è flessibile e regolabile. La lunghezza massima è di circa 38,5 cm e la minima è di 31 cm. Funziona a batterie (2 x CR2023, incluse).</p><p> Dimenzioni per Bracciale di Sicurezza LED GoFit: </br><ul><li>Altezza: 9 Cm</li><li>Larghezza: 4 Cm</li><li>Profondita': 20 Cm</li><li>Peso: 0.087 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417209475</p>","Non possiedi ancora il bracciale di sicurezza LED GoFit? Non perdertelo e pratica i tuoi sport preferiti con questo leggero e comodo bracciale progettato in Europa.</br><a href=""#maggiorni_informazioni"" title=""Bracciale di Sicurezza LED GoFit""> Maggiori Informazioni</a>",0.087,1,"Taxable Goods","Catalog, Search",44.95,,,,Bracciale-di-Sicurezza-LED-GoFit,"Bracciale di Sicurezza LED GoFit","Sport Fitness,Sport,Fitness,Abbigliamento, Accessori e Dispositivi Indossabili,Abbigliamento,,Accessori,Dispositivi,Indossabili,","Non possiedi ancora il bracciale di sicurezza LED GoFit? Non perdertelo e pratica i tuoi sport preferiti con questo leggero e comodo bracciale progettato in Europa",http://dropshipping.bigbuy.eu/imgs/G0500189_81128.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500189_81128.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8018417209475",93,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500189_81093.jpg,http://dropshipping.bigbuy.eu/imgs/G0500189_81092.jpg,http://dropshipping.bigbuy.eu/imgs/G0500189_81091.jpg","GTIN=8018417209475",,,,,,,,,, +BB-F1510306,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Abbigliamento e Scarpe,Default Category/Moda Accessori/Abbigliamento e Scarpe/Pigiami e vestaglie",base,"Coperta con Maniche Snug Snug Big Tribu Leopardato","<a id=""maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug Big Tribu""><p>Affronta</a> il freddo invernale con l'originale<strong> coperta con<strong> maniche </strong><strong>Snug Snug <strong>Big</strong> <strong>Tribu</strong></strong>! </strong>Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti. La <strong>coperta con maniche Big</strong> <strong>Tribu</strong> è dotata di tasca centrale per avere tutto a portata di mano, il telecomando della TV, il tuo libro preferito, ecc. Realizzata in morbido pile 100 % poliestere. Misure: 170 x 130 cm circa.<br /><br /><strong><a href=""http://www.snugsnug.com"">www.snugsnug.com</a></strong></p><p> </p><p> </p><p> Dimenzioni per Coperta con Maniche Snug Snug Big Tribu: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 8.5 Cm</li><li>Profondita': 26.5 Cm</li><li>Peso: 0.602 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888103530</p>","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug Big Tribu! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti.</br><a href=""#maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug Big Tribu""> Maggiori Informazioni</a>",0.602,1,"Taxable Goods","Catalog, Search",39.9,,,,Coperta-con-Maniche-Snug-Snug-Big-Tribu-Leopardato,"Coperta con Maniche Snug Snug Big Tribu Leopardato","Moda Accessori,Moda,Accessori,Abbigliamento e Scarpe,Abbigliamento,Scarpe,Pigiami e vestaglie,Pigiami,vestaglie,Colore Leopardato,Leopardato,","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug Big Tribu! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle",http://dropshipping.bigbuy.eu/imgs/F1510300_81361.jpg,,http://dropshipping.bigbuy.eu/imgs/F1510300_81361.jpg,,,,,,"2015-12-30 17:30:06",,,,,,,,,,,,,,,,,"GTIN=4899888103530",46,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/F1510300_81365.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81364.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81363.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81362.jpg","GTIN=4899888103530",,,,,,,,,, +BB-F1510307,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Abbigliamento e Scarpe,Default Category/Moda Accessori/Abbigliamento e Scarpe/Pigiami e vestaglie",base,"Coperta con Maniche Snug Snug Big Tribu Zebra","<a id=""maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug Big Tribu""><p>Affronta</a> il freddo invernale con l'originale<strong> coperta con<strong> maniche </strong><strong>Snug Snug <strong>Big</strong> <strong>Tribu</strong></strong>! </strong>Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti. La <strong>coperta con maniche Big</strong> <strong>Tribu</strong> è dotata di tasca centrale per avere tutto a portata di mano, il telecomando della TV, il tuo libro preferito, ecc. Realizzata in morbido pile 100 % poliestere. Misure: 170 x 130 cm circa.<br /><br /><strong><a href=""http://www.snugsnug.com"">www.snugsnug.com</a></strong></p><p> </p><p> </p><p> Dimenzioni per Coperta con Maniche Snug Snug Big Tribu: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 8.5 Cm</li><li>Profondita': 26.5 Cm</li><li>Peso: 0.602 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888103530</p>","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug Big Tribu! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti.</br><a href=""#maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug Big Tribu""> Maggiori Informazioni</a>",0.602,1,"Taxable Goods","Catalog, Search",39.9,,,,Coperta-con-Maniche-Snug-Snug-Big-Tribu-Zebra,"Coperta con Maniche Snug Snug Big Tribu Zebra","Moda Accessori,Moda,Accessori,Abbigliamento e Scarpe,Abbigliamento,Scarpe,Pigiami e vestaglie,Pigiami,vestaglie,Colore Zebra,Zebra,","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug Big Tribu! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle",http://dropshipping.bigbuy.eu/imgs/F1510300_81365.jpg,,http://dropshipping.bigbuy.eu/imgs/F1510300_81365.jpg,,,,,,"2016-01-21 08:26:10",,,,,,,,,,,,,,,,,"GTIN=4899888103530",486,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/F1510300_81361.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81364.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81363.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81362.jpg","GTIN=4899888103530",,,,,,,,,, +BB-F1510308,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Abbigliamento e Scarpe,Default Category/Moda Accessori/Abbigliamento e Scarpe/Pigiami e vestaglie",base,"Coperta con Maniche Snug Snug Big Tribu Dalmata","<a id=""maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug Big Tribu""><p>Affronta</a> il freddo invernale con l'originale<strong> coperta con<strong> maniche </strong><strong>Snug Snug <strong>Big</strong> <strong>Tribu</strong></strong>! </strong>Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti. La <strong>coperta con maniche Big</strong> <strong>Tribu</strong> è dotata di tasca centrale per avere tutto a portata di mano, il telecomando della TV, il tuo libro preferito, ecc. Realizzata in morbido pile 100 % poliestere. Misure: 170 x 130 cm circa.<br /><br /><strong><a href=""http://www.snugsnug.com"">www.snugsnug.com</a></strong></p><p> </p><p> </p><p> Dimenzioni per Coperta con Maniche Snug Snug Big Tribu: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 8.5 Cm</li><li>Profondita': 26.5 Cm</li><li>Peso: 0.602 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888103530</p>","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug Big Tribu! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti.</br><a href=""#maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug Big Tribu""> Maggiori Informazioni</a>",0.602,1,"Taxable Goods","Catalog, Search",39.9,,,,Coperta-con-Maniche-Snug-Snug-Big-Tribu-Dalmata,"Coperta con Maniche Snug Snug Big Tribu Dalmata","Moda Accessori,Moda,Accessori,Abbigliamento e Scarpe,Abbigliamento,Scarpe,Pigiami e vestaglie,Pigiami,vestaglie,Colore Dalmata,Dalmata,","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug Big Tribu! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle",http://dropshipping.bigbuy.eu/imgs/F1510300_81364.jpg,,http://dropshipping.bigbuy.eu/imgs/F1510300_81364.jpg,,,,,,"2016-02-22 08:16:26",,,,,,,,,,,,,,,,,"GTIN=4899888103530",307,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/F1510300_81361.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81365.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81363.jpg,http://dropshipping.bigbuy.eu/imgs/F1510300_81362.jpg","GTIN=4899888103530",,,,,,,,,, +BB-F1510302,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Abbigliamento e Scarpe,Default Category/Moda Accessori/Abbigliamento e Scarpe/Pigiami e vestaglie",base,"Coperta con Maniche Snug Snug One Big Azzurro","<a id=""maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""><p>Affronta</a> il freddo invernale con l'originale <strong>coperta con maniche </strong><strong>Snug Snug One Big</strong>! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti. La <strong>coperta con maniche </strong><strong>One Big</strong> è dotata di tasca centrale per avere tutto a portata di mano, il telecomando della TV, il tuo libro preferito, ecc. Realizzata in morbido pile 100 % poliestere. Misure: 170 x 130 cm circa.<br /><br /><strong><a href=""http://www.snugsnug.com/"">www.snugsnug.com</a><br /></strong></p><p> Dimenzioni per Coperta con Maniche Snug Snug One Big: </br><ul><li>Altezza: 26.5 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 23.7 Cm</li><li>Peso: 0.538 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102977</p>","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti.</br><a href=""#maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""> Maggiori Informazioni</a>",0.538,1,"Taxable Goods","Catalog, Search",29.9,,,,Coperta-con-Maniche-Snug-Snug-One-Big-Azzurro,"Coperta con Maniche Snug Snug One Big Azzurro","Moda Accessori,Moda,Accessori,Abbigliamento e Scarpe,Abbigliamento,Scarpe,Pigiami e vestaglie,Pigiami,vestaglie,Colore Azzurro,Azzurro,","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle m",http://dropshipping.bigbuy.eu/imgs/F1510301_81356.jpg,,http://dropshipping.bigbuy.eu/imgs/F1510301_81356.jpg,,,,,,"2015-12-28 17:26:13",,,,,,,,,,,,,,,,,"GTIN=4899888102977",538,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/F1510301_81360.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81359.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81358.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81357.jpg","GTIN=4899888102977",,,,,,,,,, +BB-F1510303,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Abbigliamento e Scarpe,Default Category/Moda Accessori/Abbigliamento e Scarpe/Pigiami e vestaglie",base,"Coperta con Maniche Snug Snug One Big Rosso","<a id=""maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""><p>Affronta</a> il freddo invernale con l'originale <strong>coperta con maniche </strong><strong>Snug Snug One Big</strong>! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti. La <strong>coperta con maniche </strong><strong>One Big</strong> è dotata di tasca centrale per avere tutto a portata di mano, il telecomando della TV, il tuo libro preferito, ecc. Realizzata in morbido pile 100 % poliestere. Misure: 170 x 130 cm circa.<br /><br /><strong><a href=""http://www.snugsnug.com/"">www.snugsnug.com</a><br /></strong></p><p> Dimenzioni per Coperta con Maniche Snug Snug One Big: </br><ul><li>Altezza: 26.5 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 23.7 Cm</li><li>Peso: 0.538 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102977</p>","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti.</br><a href=""#maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""> Maggiori Informazioni</a>",0.538,1,"Taxable Goods","Catalog, Search",29.9,,,,Coperta-con-Maniche-Snug-Snug-One-Big-Rosso,"Coperta con Maniche Snug Snug One Big Rosso","Moda Accessori,Moda,Accessori,Abbigliamento e Scarpe,Abbigliamento,Scarpe,Pigiami e vestaglie,Pigiami,vestaglie,Colore Rosso,Rosso,","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle m",http://dropshipping.bigbuy.eu/imgs/F1510301_81360.jpg,,http://dropshipping.bigbuy.eu/imgs/F1510301_81360.jpg,,,,,,"2016-01-20 10:35:30",,,,,,,,,,,,,,,,,"GTIN=4899888102977",600,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/F1510301_81356.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81359.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81358.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81357.jpg","GTIN=4899888102977",,,,,,,,,, +BB-F1510304,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Abbigliamento e Scarpe,Default Category/Moda Accessori/Abbigliamento e Scarpe/Pigiami e vestaglie",base,"Coperta con Maniche Snug Snug One Big Verde","<a id=""maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""><p>Affronta</a> il freddo invernale con l'originale <strong>coperta con maniche </strong><strong>Snug Snug One Big</strong>! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti. La <strong>coperta con maniche </strong><strong>One Big</strong> è dotata di tasca centrale per avere tutto a portata di mano, il telecomando della TV, il tuo libro preferito, ecc. Realizzata in morbido pile 100 % poliestere. Misure: 170 x 130 cm circa.<br /><br /><strong><a href=""http://www.snugsnug.com/"">www.snugsnug.com</a><br /></strong></p><p> Dimenzioni per Coperta con Maniche Snug Snug One Big: </br><ul><li>Altezza: 26.5 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 23.7 Cm</li><li>Peso: 0.538 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102977</p>","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti.</br><a href=""#maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""> Maggiori Informazioni</a>",0.538,1,"Taxable Goods","Catalog, Search",29.9,,,,Coperta-con-Maniche-Snug-Snug-One-Big-Verde,"Coperta con Maniche Snug Snug One Big Verde","Moda Accessori,Moda,Accessori,Abbigliamento e Scarpe,Abbigliamento,Scarpe,Pigiami e vestaglie,Pigiami,vestaglie,Colore Verde,Verde,","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle m",http://dropshipping.bigbuy.eu/imgs/F1510301_81359.jpg,,http://dropshipping.bigbuy.eu/imgs/F1510301_81359.jpg,,,,,,"2015-10-06 11:20:02",,,,,,,,,,,,,,,,,"GTIN=4899888102977",764,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/F1510301_81356.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81360.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81358.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81357.jpg","GTIN=4899888102977",,,,,,,,,, +BB-F1510305,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Abbigliamento e Scarpe,Default Category/Moda Accessori/Abbigliamento e Scarpe/Pigiami e vestaglie",base,"Coperta con Maniche Snug Snug One Big Rosa","<a id=""maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""><p>Affronta</a> il freddo invernale con l'originale <strong>coperta con maniche </strong><strong>Snug Snug One Big</strong>! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti. La <strong>coperta con maniche </strong><strong>One Big</strong> è dotata di tasca centrale per avere tutto a portata di mano, il telecomando della TV, il tuo libro preferito, ecc. Realizzata in morbido pile 100 % poliestere. Misure: 170 x 130 cm circa.<br /><br /><strong><a href=""http://www.snugsnug.com/"">www.snugsnug.com</a><br /></strong></p><p> Dimenzioni per Coperta con Maniche Snug Snug One Big: </br><ul><li>Altezza: 26.5 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 23.7 Cm</li><li>Peso: 0.538 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102977</p>","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle maniche che ti permettono una totale libertà di movimenti.</br><a href=""#maggiorni_informazioni"" title=""Coperta con Maniche Snug Snug One Big""> Maggiori Informazioni</a>",0.538,1,"Taxable Goods","Catalog, Search",29.9,,,,Coperta-con-Maniche-Snug-Snug-One-Big-Rosa,"Coperta con Maniche Snug Snug One Big Rosa","Moda Accessori,Moda,Accessori,Abbigliamento e Scarpe,Abbigliamento,Scarpe,Pigiami e vestaglie,Pigiami,vestaglie,Colore Rosa,Rosa,","Affronta il freddo invernale con l'originale coperta con maniche Snug Snug One Big! Un fantastico prodotto firmato Snug Snug che ti farà sentire comodo e al caldo, mentre sei sdraiato sul divano o stai facendo qualsiasi lavoro in casa, grazie alle m",http://dropshipping.bigbuy.eu/imgs/F1510301_81358.jpg,,http://dropshipping.bigbuy.eu/imgs/F1510301_81358.jpg,,,,,,"2015-12-29 06:48:13",,,,,,,,,,,,,,,,,"GTIN=4899888102977",968,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/F1510301_81356.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81360.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81359.jpg,http://dropshipping.bigbuy.eu/imgs/F1510301_81357.jpg","GTIN=4899888102977",,,,,,,,,, +BB-V1300145,,Default,simple,"Default Category/Moda Accessori,Default Category/Moda Accessori/Accessori,Default Category/Relax Tempo Libero/Accessori/Ombrelli",base,"Ombrello Star Wars con LED","<a id=""maggiorni_informazioni"" title=""Ombrello Star Wars con LED""><p>I</a> fan di Guerre Stellari impazziranno con l'<strong>ombrello Star Wars con LED</strong>!</p><ul><li>Interruttore on/off sul manico</li><li>LED di vari colori sul manico centrale</li><li>Funziona a batterie (3 x AA, incluse)</li><li>Struttura: metallo, plastica e fibra di vetro</li><li>Cupola: poliestere (pongee)</li><li>Lunghezza approssimativa: 79,5 cm</li><li>Diametro approssimativo: 95 cm</li></ul><p> Dimenzioni per Ombrello Star Wars con LED: </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 79.5 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.458 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000752317</p>","I fan di Guerre Stellari impazziranno con l'ombrello Star Wars con LED!Interruttore on/off sul manicoLED di vari colori sul manico centraleFunziona a batterie (3 x AA, incluse)Struttura: metallo, plastica e fibra di vetroCupola: poliestere (pongee)Lunghezza approssimativa: 79,5 cmDiametro approssimativo: 95 cm.</br><a href=""#maggiorni_informazioni"" title=""Ombrello Star Wars con LED""> Maggiori Informazioni</a>",0.458,1,"Taxable Goods","Catalog, Search",53.9,,,,Ombrello-Star-Wars-con-LED,"Ombrello Star Wars con LED","Moda Accessori,Moda,Accessori,Accessori,Ombrelli,","I fan di Guerre Stellari impazziranno con l'ombrello Star Wars con LED!Interruttore on/off sul manicoLED di vari colori sul manico centraleFunziona a batterie (3 x AA, incluse)Struttura: metallo, plastica e fibra di vetroCupola: poliestere (pongee)L",http://dropshipping.bigbuy.eu/imgs/V1300145_93662.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300145_93662.jpg,,,,,,"2016-09-12 07:48:53",,,,,,,,,,,,,,,,,"GTIN=7569000752317",23,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300145_93665.jpg,http://dropshipping.bigbuy.eu/imgs/V1300145_93664.jpg,http://dropshipping.bigbuy.eu/imgs/V1300145_93663.jpg,http://dropshipping.bigbuy.eu/imgs/V1300145_93661.jpg","GTIN=7569000752317",,,,,,,,,, +BB-H4530316,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Giocattoli e Giochi,Default Category/Giochi Bambini/Giocattoli e Giochi/Giochi educativi",base,"Elastici per fare bracciali con Perline di Frozen","<a id=""maggiorni_informazioni"" title=""Elastici per fare bracciali con Perline di Frozen""><p>Se</a> cerchi un <strong>gioco che intrattenga </strong>i tuoi figli e che sia l'ideale per essere alla moda, non perdere gli <strong>elastici per fare bracciali con le perline di Frozen</strong>. Contiene 130 elastici di diversi colori, perline di differenti forme con i protagonisti di Frozen, 1 gancino metallico, una chiusura a S, perline di diversi colori, 1 strumento per tenere gli elastici. Età consigliata: +5 anni. </p><p> </p><p> Dimenzioni per Elastici per fare bracciali con Perline di Frozen: </br><ul><li>Altezza: 18 Cm</li><li>Larghezza: 3.5 Cm</li><li>Profondita': 15 Cm</li><li>Peso: 0.102 Kg</li></ul></p><p>Codice Prodotto (EAN): 8714274680036</p>","Se cerchi un gioco che intrattenga i tuoi figli e che sia l'ideale per essere alla moda, non perdere gli elastici per fare bracciali con le perline di Frozen.</br><a href=""#maggiorni_informazioni"" title=""Elastici per fare bracciali con Perline di Frozen""> Maggiori Informazioni</a>",0.102,1,"Taxable Goods","Catalog, Search",35,,,,Elastici-per-fare-bracciali-con-Perline-di-Frozen,"Elastici per fare bracciali con Perline di Frozen","Giochi Bambini,Giochi,Bambini,Giocattoli e Giochi,Giocattoli,Giochi,Giochi educativi,Giochi,educativi,","Se cerchi un gioco che intrattenga i tuoi figli e che sia l'ideale per essere alla moda, non perdere gli elastici per fare bracciali con le perline di Frozen",http://dropshipping.bigbuy.eu/imgs/H4530316_93063.jpg,,http://dropshipping.bigbuy.eu/imgs/H4530316_93063.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8714274680036",78,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/H4530316_93067.jpg,http://dropshipping.bigbuy.eu/imgs/H4530316_93066.jpg,http://dropshipping.bigbuy.eu/imgs/H4530316_93065.jpg,http://dropshipping.bigbuy.eu/imgs/H4530316_93064.jpg","GTIN=8714274680036",,,,,,,,,, +BB-V1300134,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Ombrelli e cappellini per bambini",base,"Berretto per Bambini Cars Rosso","<a id=""maggiorni_informazioni"" title=""Berretto per Bambini Cars""><p>Vuoi</a> sorprendere i piccoli della tua casa? Lightning McQueen proteggerà i più piccoli della casa dal sole di questa estate! Non perderti il <strong>berretto per bambini Cars</strong>. Dimensioni 54-56 cm. Misure della visiera: 14 x 6,5 cm circa. Composizione: 65% cotone e 35% poliestere</p><p> Dimenzioni per Berretto per Bambini Cars: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 17 Cm</li><li>Profondita': 21 Cm</li><li>Peso: 0.048 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934797318</p>","Vuoi sorprendere i piccoli della tua casa? Lightning McQueen proteggerà i più piccoli della casa dal sole di questa estate! Non perderti il berretto per bambini Cars.</br><a href=""#maggiorni_informazioni"" title=""Berretto per Bambini Cars""> Maggiori Informazioni</a>",0.048,1,"Taxable Goods","Catalog, Search",7.9,,,,Berretto-per-Bambini-Cars-Rosso,"Berretto per Bambini Cars Rosso","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Ombrelli e cappellini per bambini,Ombrelli,cappellini,bambini,Colore Rosso,Rosso,","Vuoi sorprendere i piccoli della tua casa? Lightning McQueen proteggerà i più piccoli della casa dal sole di questa estate! Non perderti il berretto per bambini Cars",http://dropshipping.bigbuy.eu/imgs/V1300133_91571.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300133_91571.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934797318",87,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300133_91229.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91197.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91196.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91195.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91194.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91193.jpg","GTIN=8427934797318",,,,,,,,,, +BB-V1300135,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Ombrelli e cappellini per bambini",base,"Berretto per Bambini Cars Nero","<a id=""maggiorni_informazioni"" title=""Berretto per Bambini Cars""><p>Vuoi</a> sorprendere i piccoli della tua casa? Lightning McQueen proteggerà i più piccoli della casa dal sole di questa estate! Non perderti il <strong>berretto per bambini Cars</strong>. Dimensioni 54-56 cm. Misure della visiera: 14 x 6,5 cm circa. Composizione: 65% cotone e 35% poliestere</p><p> Dimenzioni per Berretto per Bambini Cars: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 17 Cm</li><li>Profondita': 21 Cm</li><li>Peso: 0.048 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934797301</p>","Vuoi sorprendere i piccoli della tua casa? Lightning McQueen proteggerà i più piccoli della casa dal sole di questa estate! Non perderti il berretto per bambini Cars.</br><a href=""#maggiorni_informazioni"" title=""Berretto per Bambini Cars""> Maggiori Informazioni</a>",0.048,1,"Taxable Goods","Catalog, Search",7.9,,,,Berretto-per-Bambini-Cars-Nero,"Berretto per Bambini Cars Nero","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Ombrelli e cappellini per bambini,Ombrelli,cappellini,bambini,Colore Nero,Nero,","Vuoi sorprendere i piccoli della tua casa? Lightning McQueen proteggerà i più piccoli della casa dal sole di questa estate! Non perderti il berretto per bambini Cars",http://dropshipping.bigbuy.eu/imgs/V1300133_91229.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300133_91229.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934797301",123,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300133_91571.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91197.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91196.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91195.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91194.jpg,http://dropshipping.bigbuy.eu/imgs/V1300133_91193.jpg","GTIN=8427934797301",,,,,,,,,, +BB-V1300138,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Ombrelli e cappellini per bambini",base,"Cappello per Bambini Batman vs Superman Blu Marino","<a id=""maggiorni_informazioni"" title=""Cappello per Bambini Batman vs Superman""><p>A</a> quale bambino non piace vantarsi dei suoi <strong>accessori moda</strong>? Sorprendi i più piccoli con il <strong>cappello per bambini Batman vs Superman</strong><strong> </strong>e quest'estate fai sì che siano ben protetti dai raggi solari. Taglia 52-54 cm. Dimensioni della visiera: 15 x 6,5 cm circa. Composizione: 65 % cotone e 35 % poliestere.</p><p> Dimenzioni per Cappello per Bambini Batman vs Superman: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 19 Cm</li><li>Profondita': 20 Cm</li><li>Peso: 0.048 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934824359</p>","A quale bambino non piace vantarsi dei suoi accessori moda? Sorprendi i più piccoli con il cappello per bambini Batman vs Superman e quest'estate fai sì che siano ben protetti dai raggi solari.</br><a href=""#maggiorni_informazioni"" title=""Cappello per Bambini Batman vs Superman""> Maggiori Informazioni</a>",0.048,1,"Taxable Goods","Catalog, Search",12.5,,,,Cappello-per-Bambini-Batman-vs-Superman-Blu Marino,"Cappello per Bambini Batman vs Superman Blu Marino","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Ombrelli e cappellini per bambini,Ombrelli,cappellini,bambini,Colore Blu Marino,Blu Marino,","A quale bambino non piace vantarsi dei suoi accessori moda? Sorprendi i più piccoli con il cappello per bambini Batman vs Superman e quest'estate fai sì che siano ben protetti dai raggi solari",http://dropshipping.bigbuy.eu/imgs/V1300136_91230.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300136_91230.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934824359",98,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300136_91231.jpg,http://dropshipping.bigbuy.eu/imgs/V1300136_91201.jpg,http://dropshipping.bigbuy.eu/imgs/V1300136_91200.jpg,http://dropshipping.bigbuy.eu/imgs/V1300136_91199.jpg","GTIN=8427934824359",,,,,,,,,, +BB-V1300137,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Ombrelli e cappellini per bambini",base,"Cappello per Bambini Batman vs Superman Grigio","<a id=""maggiorni_informazioni"" title=""Cappello per Bambini Batman vs Superman""><p>A</a> quale bambino non piace vantarsi dei suoi <strong>accessori moda</strong>? Sorprendi i più piccoli con il <strong>cappello per bambini Batman vs Superman</strong><strong> </strong>e quest'estate fai sì che siano ben protetti dai raggi solari. Taglia 52-54 cm. Dimensioni della visiera: 15 x 6,5 cm circa. Composizione: 65 % cotone e 35 % poliestere.</p><p> Dimenzioni per Cappello per Bambini Batman vs Superman: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 19 Cm</li><li>Profondita': 20 Cm</li><li>Peso: 0.048 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934824335</p>","A quale bambino non piace vantarsi dei suoi accessori moda? Sorprendi i più piccoli con il cappello per bambini Batman vs Superman e quest'estate fai sì che siano ben protetti dai raggi solari.</br><a href=""#maggiorni_informazioni"" title=""Cappello per Bambini Batman vs Superman""> Maggiori Informazioni</a>",0.048,1,"Taxable Goods","Catalog, Search",12.5,,,,Cappello-per-Bambini-Batman-vs-Superman-Grigio,"Cappello per Bambini Batman vs Superman Grigio","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Ombrelli e cappellini per bambini,Ombrelli,cappellini,bambini,Colore Grigio,Grigio,","A quale bambino non piace vantarsi dei suoi accessori moda? Sorprendi i più piccoli con il cappello per bambini Batman vs Superman e quest'estate fai sì che siano ben protetti dai raggi solari",http://dropshipping.bigbuy.eu/imgs/V1300136_91231.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300136_91231.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934824335",102,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300136_91230.jpg,http://dropshipping.bigbuy.eu/imgs/V1300136_91201.jpg,http://dropshipping.bigbuy.eu/imgs/V1300136_91200.jpg,http://dropshipping.bigbuy.eu/imgs/V1300136_91199.jpg","GTIN=8427934824335",,,,,,,,,, +BB-V1300125,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Ombrelli e cappellini per bambini",base,"Berretto per Bambini Avengers Rosso","<a id=""maggiorni_informazioni"" title=""Berretto per Bambini Avengers""><p>A</a> quale bambino non piace mostrare gli <strong>accessori di moda</strong>? Sorprendili con il<strong> </strong><strong>berretto per bambini Avengers</strong> e proteggili dai raggi del sole di questa estate. Taglia 52-54 cm. Dimensioni della visiera: 15 x 6,5 cm circa. Composizione: 65% cotone e 35% poliestere.</p><p> Dimenzioni per Berretto per Bambini Avengers: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 20 Cm</li><li>Profondita': 18 Cm</li><li>Peso: 0.048 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934792252</p>","A quale bambino non piace mostrare gli accessori di moda? Sorprendili con il berretto per bambini Avengers e proteggili dai raggi del sole di questa estate.</br><a href=""#maggiorni_informazioni"" title=""Berretto per Bambini Avengers""> Maggiori Informazioni</a>",0.048,1,"Taxable Goods","Catalog, Search",10.9,,,,Berretto-per-Bambini-Avengers-Rosso,"Berretto per Bambini Avengers Rosso","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Ombrelli e cappellini per bambini,Ombrelli,cappellini,bambini,Colore Rosso,Rosso,","A quale bambino non piace mostrare gli accessori di moda? Sorprendili con il berretto per bambini Avengers e proteggili dai raggi del sole di questa estate",http://dropshipping.bigbuy.eu/imgs/V1300124_91179.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300124_91179.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934792252",101,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300124_91180.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91178.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91177.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91176.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91175.jpg","GTIN=8427934792252",,,,,,,,,, +BB-V1300126,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Ombrelli e cappellini per bambini",base,"Berretto per Bambini Avengers Nero","<a id=""maggiorni_informazioni"" title=""Berretto per Bambini Avengers""><p>A</a> quale bambino non piace mostrare gli <strong>accessori di moda</strong>? Sorprendili con il<strong> </strong><strong>berretto per bambini Avengers</strong> e proteggili dai raggi del sole di questa estate. Taglia 52-54 cm. Dimensioni della visiera: 15 x 6,5 cm circa. Composizione: 65% cotone e 35% poliestere.</p><p> Dimenzioni per Berretto per Bambini Avengers: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 20 Cm</li><li>Profondita': 18 Cm</li><li>Peso: 0.048 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934792269</p>","A quale bambino non piace mostrare gli accessori di moda? Sorprendili con il berretto per bambini Avengers e proteggili dai raggi del sole di questa estate.</br><a href=""#maggiorni_informazioni"" title=""Berretto per Bambini Avengers""> Maggiori Informazioni</a>",0.048,1,"Taxable Goods","Catalog, Search",10.9,,,,Berretto-per-Bambini-Avengers-Nero,"Berretto per Bambini Avengers Nero","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Ombrelli e cappellini per bambini,Ombrelli,cappellini,bambini,Colore Nero,Nero,","A quale bambino non piace mostrare gli accessori di moda? Sorprendili con il berretto per bambini Avengers e proteggili dai raggi del sole di questa estate",http://dropshipping.bigbuy.eu/imgs/V1300124_91180.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300124_91180.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934792269",92,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300124_91179.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91178.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91177.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91176.jpg,http://dropshipping.bigbuy.eu/imgs/V1300124_91175.jpg","GTIN=8427934792269",,,,,,,,,, +BB-V0500179,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Stoviglie per bambini",base,"Posate Bambini Disney (5 pezzi) Minnie","<a id=""maggiorni_informazioni"" title=""Posate Bambini Disney (5 pezzi)""><p>Quando</a> i piccoli di casa hanno già familiarizzato con il fatto di mangiare da soli, bisogna fare il passo successivo e comprare loro il <strong>set per mangiare </strong>da grandi, come le <strong>posate per bambini Disney (5 pezzi). </strong>Include: 1 piatto fondo, 1 piatto piano, 1 cucchiaio, 1 forchetta e 1 tazza.</p><ul><li>Età raccomandata: +12 mesi</li><li>Realizzato in polipropilene (senza BPA)</li><li>Adatto a lavastoviglie e microonde</li><li>Dimensioni del piatto fondo (diametro x altura): 15,5 x 3 cm circa</li><li>Dimensioni del piatto piano (diametro x altura): 22 x 1,5 cm circa</li><li>Lunghezza del cucchiaio: 15,5 cm circa</li><li>Lunghezza della forchetta: 15,5 cm circa</li><li>Dimensioni della tazza: 11 x 8,5 x 8,5 cm circa</li><li>Capacità della tazza: circa 250 ml</li><li>Conforme alla normativa UNE-EN 14372 (requisiti di sicurezza per gli articoli di puericultura per l'alimentazione: servizio di posate e utensili)</li><li>Conforme alla normativa UNE-EN 14350 (requisiti di sicurezza per gli articoli di puericultura per l'alimentazione liquida)</li></ul><p> Dimenzioni per Posate Bambini Disney (5 pezzi): </br><ul><li>Altezza: 27.5 Cm</li><li>Larghezza: 9.7 Cm</li><li>Profondita': 9 Cm</li><li>Peso: 0.487 Kg</li></ul></p><p>Codice Prodotto (EAN): 3662332013348</p>","Quando i piccoli di casa hanno già familiarizzato con il fatto di mangiare da soli, bisogna fare il passo successivo e comprare loro il set per mangiare da grandi, come le posate per bambini Disney (5 pezzi).</br><a href=""#maggiorni_informazioni"" title=""Posate Bambini Disney (5 pezzi)""> Maggiori Informazioni</a>",0.487,1,"Taxable Goods","Catalog, Search",41.5,,,,Posate-Bambini-Disney-(5-pezzi)-Minnie,"Posate Bambini Disney (5 pezzi) Minnie","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Stoviglie per bambini,Stoviglie,bambini,Modello Minnie,Minnie,","Quando i piccoli di casa hanno già familiarizzato con il fatto di mangiare da soli, bisogna fare il passo successivo e comprare loro il set per mangiare da grandi, come le posate per bambini Disney (5 pezzi)",http://dropshipping.bigbuy.eu/imgs/V0500178_92050.jpg,,http://dropshipping.bigbuy.eu/imgs/V0500178_92050.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3662332013348",34,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0500178_92051.jpg,http://dropshipping.bigbuy.eu/imgs/V0500178_92049.jpg","GTIN=3662332013348",,,,,,,,,, +BB-V0500180,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Stoviglie per bambini",base,"Posate Bambini Disney (5 pezzi) Mickey","<a id=""maggiorni_informazioni"" title=""Posate Bambini Disney (5 pezzi)""><p>Quando</a> i piccoli di casa hanno già familiarizzato con il fatto di mangiare da soli, bisogna fare il passo successivo e comprare loro il <strong>set per mangiare </strong>da grandi, come le <strong>posate per bambini Disney (5 pezzi). </strong>Include: 1 piatto fondo, 1 piatto piano, 1 cucchiaio, 1 forchetta e 1 tazza.</p><ul><li>Età raccomandata: +12 mesi</li><li>Realizzato in polipropilene (senza BPA)</li><li>Adatto a lavastoviglie e microonde</li><li>Dimensioni del piatto fondo (diametro x altura): 15,5 x 3 cm circa</li><li>Dimensioni del piatto piano (diametro x altura): 22 x 1,5 cm circa</li><li>Lunghezza del cucchiaio: 15,5 cm circa</li><li>Lunghezza della forchetta: 15,5 cm circa</li><li>Dimensioni della tazza: 11 x 8,5 x 8,5 cm circa</li><li>Capacità della tazza: circa 250 ml</li><li>Conforme alla normativa UNE-EN 14372 (requisiti di sicurezza per gli articoli di puericultura per l'alimentazione: servizio di posate e utensili)</li><li>Conforme alla normativa UNE-EN 14350 (requisiti di sicurezza per gli articoli di puericultura per l'alimentazione liquida)</li></ul><p> Dimenzioni per Posate Bambini Disney (5 pezzi): </br><ul><li>Altezza: 27.5 Cm</li><li>Larghezza: 9.7 Cm</li><li>Profondita': 9 Cm</li><li>Peso: 0.487 Kg</li></ul></p><p>Codice Prodotto (EAN): 3662332013355</p>","Quando i piccoli di casa hanno già familiarizzato con il fatto di mangiare da soli, bisogna fare il passo successivo e comprare loro il set per mangiare da grandi, come le posate per bambini Disney (5 pezzi).</br><a href=""#maggiorni_informazioni"" title=""Posate Bambini Disney (5 pezzi)""> Maggiori Informazioni</a>",0.487,1,"Taxable Goods","Catalog, Search",41.5,,,,Posate-Bambini-Disney-(5-pezzi)-Mickey,"Posate Bambini Disney (5 pezzi) Mickey","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Stoviglie per bambini,Stoviglie,bambini,Modello Mickey,Mickey,","Quando i piccoli di casa hanno già familiarizzato con il fatto di mangiare da soli, bisogna fare il passo successivo e comprare loro il set per mangiare da grandi, come le posate per bambini Disney (5 pezzi)",http://dropshipping.bigbuy.eu/imgs/V0500178_92051.jpg,,http://dropshipping.bigbuy.eu/imgs/V0500178_92051.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3662332013355",37,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0500178_92050.jpg,http://dropshipping.bigbuy.eu/imgs/V0500178_92049.jpg","GTIN=3662332013355",,,,,,,,,, +BB-V1300179,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Ombrelli e cappellini per bambini",base,"Ombrello per Bambini Pieghevole Star Wars","<a id=""maggiorni_informazioni"" title=""Ombrello per Bambini Pieghevole Star Wars""><p>Ti</a> presentiamo l'ombrello più galattico del pianeta, l'<strong><strong>ombrello per bambini pieghevole Star Wars</strong></strong>! Perfetto come <strong>regalo per bambini.</strong></p><ul><li>Struttura: 75 % metallo, 25 % plastica</li><li>Cupola: 100 % poliestere</li><li>Lunghezza: circa 23-52 cm</li><li>Diametro: circa 85 cm</li><li>Custodia inclusa</li></ul><p> </p><p> Dimenzioni per Ombrello per Bambini Pieghevole Star Wars: </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 6.5 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.255 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000732739</p>","Ti presentiamo l'ombrello più galattico del pianeta, l'ombrello per bambini pieghevole Star Wars! Perfetto come regalo per bambini.</br><a href=""#maggiorni_informazioni"" title=""Ombrello per Bambini Pieghevole Star Wars""> Maggiori Informazioni</a>",0.255,1,"Taxable Goods","Catalog, Search",24.5,,,,Ombrello-per-Bambini-Pieghevole-Star-Wars,"Ombrello per Bambini Pieghevole Star Wars","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Ombrelli e cappellini per bambini,Ombrelli,cappellini,bambini,","Ti presentiamo l'ombrello più galattico del pianeta, l'ombrello per bambini pieghevole Star Wars! Perfetto come regalo per bambini",http://dropshipping.bigbuy.eu/imgs/V1300179_101280.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300179_101280.jpg,,,,,,"2016-08-29 12:58:30",,,,,,,,,,,,,,,,,"GTIN=7569000732739",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300179_101281.jpg,http://dropshipping.bigbuy.eu/imgs/V1300179_101279.jpg,http://dropshipping.bigbuy.eu/imgs/V1300179_101278.jpg,http://dropshipping.bigbuy.eu/imgs/V1300179_101277.jpg","GTIN=7569000732739",,,,,,,,,, +BB-V1300195,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Passeggiate e viaggi",base,"Borsa Termica Porta Merenda Rubble (PAW Patrol)","<a id=""maggiorni_informazioni"" title=""Borsa Termica Porta Merenda Rubble (PAW Patrol)""><p>Ti</a> presentiamo la <strong><strong>borsa termica porta merende Rubble (PAW Patrol)</strong></strong>! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla. Ideale per portare con sé il pranzo e la merenda.</p><ul><li>Dimensioni: circa 23 x 19 x 8 cm</li><li>Composizione: poliestere, schiuma di poliuretano e PEVA (polietilene di acetato di vinilo)</li></ul><p> Dimenzioni per Borsa Termica Porta Merenda Rubble (PAW Patrol): </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 22 Cm</li><li>Profondita': 26 Cm</li><li>Peso: 0.196 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000732890</p>","Ti presentiamo la borsa termica porta merende Rubble (PAW Patrol)! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla.</br><a href=""#maggiorni_informazioni"" title=""Borsa Termica Porta Merenda Rubble (PAW Patrol)""> Maggiori Informazioni</a>",0.196,1,"Taxable Goods","Catalog, Search",26.9,,,,Borsa-Termica-Porta-Merenda-Rubble-(PAW-Patrol),"Borsa Termica Porta Merenda Rubble (PAW Patrol)","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Passeggiate e viaggi,Passeggiate,viaggi,","Ti presentiamo la borsa termica porta merende Rubble (PAW Patrol)! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla",http://dropshipping.bigbuy.eu/imgs/V1300195_101259.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300195_101259.jpg,,,,,,"2016-08-26 13:36:55",,,,,,,,,,,,,,,,,"GTIN=7569000732890",59,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300195_101258.jpg,http://dropshipping.bigbuy.eu/imgs/V1300195_101257.jpg,http://dropshipping.bigbuy.eu/imgs/V1300195_101256.jpg","GTIN=7569000732890",,,,,,,,,, +BB-V1300196,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Passeggiate e viaggi",base,"Borsa Termica Porta Merenda Everest (PAW Patrol)","<a id=""maggiorni_informazioni"" title=""Borsa Termica Porta Merenda Everest (PAW Patrol)""><p>Scopri</a> la<strong> <strong>borsa termica porta merenda Everest (PAW Patrol)</strong></strong> che sta facendo furore tra i bambini! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla. Perfetta per portare con sé il pranzo e la merenda.</p><ul><li>Dimensioni: circa 23 x 19 x 8 cm</li><li>Composizione: poliestere, schiuma di poliuretano e PEVA (polietilene di acetato di vinilo)</li></ul><p> Dimenzioni per Borsa Termica Porta Merenda Everest (PAW Patrol): </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 22 Cm</li><li>Profondita': 26 Cm</li><li>Peso: 0.196 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000732906</p>","Scopri la borsa termica porta merenda Everest (PAW Patrol) che sta facendo furore tra i bambini! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla.</br><a href=""#maggiorni_informazioni"" title=""Borsa Termica Porta Merenda Everest (PAW Patrol)""> Maggiori Informazioni</a>",0.196,1,"Taxable Goods","Catalog, Search",26.9,,,,Borsa-Termica-Porta-Merenda-Everest-(PAW-Patrol),"Borsa Termica Porta Merenda Everest (PAW Patrol)","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Passeggiate e viaggi,Passeggiate,viaggi,","Scopri la borsa termica porta merenda Everest (PAW Patrol) che sta facendo furore tra i bambini! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla",http://dropshipping.bigbuy.eu/imgs/V1300196_101260.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300196_101260.jpg,,,,,,"2016-08-26 13:36:36",,,,,,,,,,,,,,,,,"GTIN=7569000732906",51,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300196_101263.jpg,http://dropshipping.bigbuy.eu/imgs/V1300196_101262.jpg,http://dropshipping.bigbuy.eu/imgs/V1300196_101261.jpg","GTIN=7569000732906",,,,,,,,,, +BB-V1300198,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Neonati e Bambini,Default Category/Giochi Bambini/Neonati e Bambini/Passeggiate e viaggi",base,"Borsa Termica Porta Merenda Frozen","<a id=""maggiorni_informazioni"" title=""Borsa Termica Porta Merenda Frozen""><p>Tutte</a> le bambine vogliono subito la <strong><strong><strong>borsa termica porta merenda</strong> Frozen</strong></strong>! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla. Perfetta per portare con sé il pranzo e la merenda.</p><ul><li>Dimensioni: circa 23 x 19 x 8 cm</li><li>Composizione: poliestere, schiuma di poliuretano e PEVA (polietilene di acetato di vinilo)</li></ul><p> Dimenzioni per Borsa Termica Porta Merenda Frozen: </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 22 Cm</li><li>Profondita': 26 Cm</li><li>Peso: 0.196 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000732920</p>","Tutte le bambine vogliono subito la borsa termica porta merenda Frozen! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla.</br><a href=""#maggiorni_informazioni"" title=""Borsa Termica Porta Merenda Frozen""> Maggiori Informazioni</a>",0.196,1,"Taxable Goods","Catalog, Search",26.9,,,,Borsa-Termica-Porta-Merenda-Frozen,"Borsa Termica Porta Merenda Frozen","Giochi Bambini,Giochi,Bambini,Neonati e Bambini,Neonati,Bambini,Passeggiate e viaggi,Passeggiate,viaggi,","Tutte le bambine vogliono subito la borsa termica porta merenda Frozen! Dispone di un disegno in rilievo nella parte frontale (gomma EVA), cerniera, manico e due cinte regolabili per appenderla",http://dropshipping.bigbuy.eu/imgs/V1300198_101268.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300198_101268.jpg,,,,,,"2016-08-30 07:32:17",,,,,,,,,,,,,,,,,"GTIN=7569000732920",52,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300198_101271.jpg,http://dropshipping.bigbuy.eu/imgs/V1300198_101270.jpg,http://dropshipping.bigbuy.eu/imgs/V1300198_101269.jpg","GTIN=7569000732920",,,,,,,,,, +BB-V1300204,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Materiale Scolastico,Default Category/Giochi Bambini/Materiale Scolastico/Astucci e portapenne",base,"Astuccio Scuola 3D Frozen","<a id=""maggiorni_informazioni"" title=""Astuccio Scuola 3D Frozen""><p>Le</a> piccola fan delle principesse Anna ed Elsa non possono tornare a scuola senza l'<strong>astuccio scuola</strong> <strong>3D Frozen</strong>!</p><ul><li>Cinque scompartimenti separati</li><li>Due cerniere</li><li>Dimensioni: 21,5 x 12 x 10 cm circa</li><li>Composizione: poliestere</li></ul><p> Dimenzioni per Astuccio Scuola 3D Frozen: </br><ul><li>Altezza: 2 Cm</li><li>Larghezza: 24 Cm</li><li>Profondita': 14 Cm</li><li>Peso: 0.099 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000733248</p>","Le piccola fan delle principesse Anna ed Elsa non possono tornare a scuola senza l'astuccio scuola 3D Frozen!Cinque scompartimenti separatiDue cerniereDimensioni: 21,5 x 12 x 10 cm circaComposizione: poliestere.</br><a href=""#maggiorni_informazioni"" title=""Astuccio Scuola 3D Frozen""> Maggiori Informazioni</a>",0.099,1,"Taxable Goods","Catalog, Search",19.9,,,,Astuccio-Scuola-3D-Frozen,"Astuccio Scuola 3D Frozen","Giochi Bambini,Giochi,Bambini,Materiale Scolastico,Materiale,Scolastico,Astucci e portapenne,Astucci,portapenne,","Le piccola fan delle principesse Anna ed Elsa non possono tornare a scuola senza l'astuccio scuola 3D Frozen!Cinque scompartimenti separatiDue cerniereDimensioni: 21,5 x 12 x 10 cm circaComposizione: poliestere",http://dropshipping.bigbuy.eu/imgs/V1300204_102661.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300204_102661.jpg,,,,,,"2016-09-14 07:51:11",,,,,,,,,,,,,,,,,"GTIN=7569000733248",138,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300204_102663.jpg,http://dropshipping.bigbuy.eu/imgs/V1300204_102662.jpg","GTIN=7569000733248",,,,,,,,,, +BB-V1300208,,Default,simple,"Default Category/Giochi Bambini,Default Category/Giochi Bambini/Materiale Scolastico,Default Category/Giochi Bambini/Materiale Scolastico/Zaini scuola",base,"Zaino-Sacca Frozen","<a id=""maggiorni_informazioni"" title=""Zaino-Sacca Frozen""><p>Lo</a> <strong>zaino-sacca Frozen </strong>è lo zaino che sta facendo furore tra le bambine!</p><ul><li>Realizzato in poliestere</li><li>Manico superiore e cinghie con velcro</li><li>Tasca frontale con cerniera</li><li>Dimensioni: circa 33 x 44 cm</li></ul><p> </p><p> Dimenzioni per Zaino-Sacca Frozen: </br><ul><li>Altezza: 0.5 Cm</li><li>Larghezza: 37 Cm</li><li>Profondita': 46 Cm</li><li>Peso: 0.138 Kg</li></ul></p><p>Codice Prodotto (EAN): 7569000733286</p>","Lo zaino-sacca Frozen è lo zaino che sta facendo furore tra le bambine!Realizzato in poliestereManico superiore e cinghie con velcroTasca frontale con cernieraDimensioni: circa 33 x 44 cm .</br><a href=""#maggiorni_informazioni"" title=""Zaino-Sacca Frozen""> Maggiori Informazioni</a>",0.138,1,"Taxable Goods","Catalog, Search",24.9,,,,Zaino-Sacca-Frozen,"Zaino-Sacca Frozen","Giochi Bambini,Giochi,Bambini,Materiale Scolastico,Materiale,Scolastico,Zaini scuola,Zaini,scuola,","Lo zaino-sacca Frozen è lo zaino che sta facendo furore tra le bambine!Realizzato in poliestereManico superiore e cinghie con velcroTasca frontale con cernieraDimensioni: circa 33 x 44 cm ",http://dropshipping.bigbuy.eu/imgs/V1300208_102667.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300208_102667.jpg,,,,,,"2016-09-14 07:50:58",,,,,,,,,,,,,,,,,"GTIN=7569000733286",141,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300208_102669.jpg,http://dropshipping.bigbuy.eu/imgs/V1300208_102668.jpg","GTIN=7569000733286",,,,,,,,,, +BB-I4115041,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cover e custodie",base,"Custodia Impermeabile per Cellulare WpShield Azzurro","<a id=""maggiorni_informazioni"" title=""Custodia Impermeabile per Cellulare WpShield ""><p>Sei</a> una di quelle persone che portano il loro smartphone ovunque? Se desideri che il tuo telefono venga protetto dalla sporcizia, sabbia, graffi e anche dall'acqua, non perderti la <strong>custodia impermeabile per cellulare WpShield</strong>. Con questa custodia impermeabile potrai portare il telefono ovunque ti piaccia, anche per un tuffo in piscina o sul mare.<br /><br /><a href=""http://www.waterproofshield.com/""><strong>www.waterproofshield.com</strong></a><br /><br />Questa custodia impermeabile dispone sia di un cinturino con chiusura a velcro (lunghezza massima: circa 37 cm) e un cavo con chiusura di sicurezza da indossare al collo (lunghezza massima: circa 60 cm). Composizione: PVC (Spessore: circa 3 mm). Dimensioni: circa 10,5 x 15,5 cm.</p><p> Dimenzioni per Custodia Impermeabile per Cellulare WpShield : </br><ul><li>Altezza: 20 Cm</li><li>Larghezza: 11.1 Cm</li><li>Profondita': 1.9 Cm</li><li>Peso: 0.06 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888106722</p>","Sei una di quelle persone che portano il loro smartphone ovunque? Se desideri che il tuo telefono venga protetto dalla sporcizia, sabbia, graffi e anche dall'acqua, non perderti la custodia impermeabile per cellulare WpShield.</br><a href=""#maggiorni_informazioni"" title=""Custodia Impermeabile per Cellulare WpShield ""> Maggiori Informazioni</a>",0.06,1,"Taxable Goods","Catalog, Search",14.5,,,,Custodia-Impermeabile-per-Cellulare-WpShield-Azzurro,"Custodia Impermeabile per Cellulare WpShield Azzurro","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cover e custodie,Cover,custodie,Colore Azzurro,Azzurro,","Sei una di quelle persone che portano il loro smartphone ovunque? Se desideri che il tuo telefono venga protetto dalla sporcizia, sabbia, graffi e anche dall'acqua, non perderti la custodia impermeabile per cellulare WpShield",http://dropshipping.bigbuy.eu/imgs/I4115040_78768.jpg,,http://dropshipping.bigbuy.eu/imgs/I4115040_78768.jpg,,,,,,"2015-12-21 12:09:49",,,,,,,,,,,,,,,,,"GTIN=4899888106722",4125,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I4115040_78770.jpg,http://dropshipping.bigbuy.eu/imgs/I4115040_78769.jpg,http://dropshipping.bigbuy.eu/imgs/I4115040_78767.jpg","GTIN=4899888106722",,,,,,,,,, +BB-I4115042,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cover e custodie",base,"Custodia Impermeabile per Cellulare WpShield Bianco","<a id=""maggiorni_informazioni"" title=""Custodia Impermeabile per Cellulare WpShield ""><p>Sei</a> una di quelle persone che portano il loro smartphone ovunque? Se desideri che il tuo telefono venga protetto dalla sporcizia, sabbia, graffi e anche dall'acqua, non perderti la <strong>custodia impermeabile per cellulare WpShield</strong>. Con questa custodia impermeabile potrai portare il telefono ovunque ti piaccia, anche per un tuffo in piscina o sul mare.<br /><br /><a href=""http://www.waterproofshield.com/""><strong>www.waterproofshield.com</strong></a><br /><br />Questa custodia impermeabile dispone sia di un cinturino con chiusura a velcro (lunghezza massima: circa 37 cm) e un cavo con chiusura di sicurezza da indossare al collo (lunghezza massima: circa 60 cm). Composizione: PVC (Spessore: circa 3 mm). Dimensioni: circa 10,5 x 15,5 cm.</p><p> Dimenzioni per Custodia Impermeabile per Cellulare WpShield : </br><ul><li>Altezza: 20 Cm</li><li>Larghezza: 11.1 Cm</li><li>Profondita': 1.9 Cm</li><li>Peso: 0.06 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888106739</p>","Sei una di quelle persone che portano il loro smartphone ovunque? Se desideri che il tuo telefono venga protetto dalla sporcizia, sabbia, graffi e anche dall'acqua, non perderti la custodia impermeabile per cellulare WpShield.</br><a href=""#maggiorni_informazioni"" title=""Custodia Impermeabile per Cellulare WpShield ""> Maggiori Informazioni</a>",0.06,1,"Taxable Goods","Catalog, Search",14.5,,,,Custodia-Impermeabile-per-Cellulare-WpShield-Bianco,"Custodia Impermeabile per Cellulare WpShield Bianco","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cover e custodie,Cover,custodie,Colore Bianco,Bianco,","Sei una di quelle persone che portano il loro smartphone ovunque? Se desideri che il tuo telefono venga protetto dalla sporcizia, sabbia, graffi e anche dall'acqua, non perderti la custodia impermeabile per cellulare WpShield",http://dropshipping.bigbuy.eu/imgs/I4115040_78770.jpg,,http://dropshipping.bigbuy.eu/imgs/I4115040_78770.jpg,,,,,,"2015-12-21 12:10:05",,,,,,,,,,,,,,,,,"GTIN=4899888106739",4321,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I4115040_78768.jpg,http://dropshipping.bigbuy.eu/imgs/I4115040_78769.jpg,http://dropshipping.bigbuy.eu/imgs/I4115040_78767.jpg","GTIN=4899888106739",,,,,,,,,, +BB-I4115044,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Batterie, Caricatori, Adattatori",base,"Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken Bianco","<a id=""maggiorni_informazioni"" title=""Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken ""><p>Non</a> uscire di casa senza la <strong>doppia porta USB con presa elettrica e caricabatteria da auto Pocken</strong> per auto! Puoi connetterla in auto o alla rete elettrica per ricaricare i dispositivi mobili, poichè include sia un adattatore per auto che una spina. Questo caricatore con doppio ingresso USB è molto pratico e semplice da usare. Dimensioni approssimative: 6 x 6 x 3,5 cm.</p><p><strong><a href=""http://www.pockenrg.com"">www.pockenrg.com</a>  </strong></p><div><div>Caratteristiche tecniche:</div><ul><li>Ingresso AC: 100-240 V / 0.15 A / 50-60 Hz</li><li>Ingresso DC: 12-24 V / 0.35 A</li><li>Uscita DC: +5  V / 1 A</li></ul></div><p> Dimenzioni per Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken : </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 6.5 Cm</li><li>Profondita': 6.5 Cm</li><li>Peso: 0.077 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888106944</p>","Non uscire di casa senza la doppia porta USB con presa elettrica e caricabatteria da auto Pocken per auto! Puoi connetterla in auto o alla rete elettrica per ricaricare i dispositivi mobili, poichè include sia un adattatore per auto che una spina.</br><a href=""#maggiorni_informazioni"" title=""Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken ""> Maggiori Informazioni</a>",0.077,1,"Taxable Goods","Catalog, Search",29.99,,,,Doppia-Porta-USB-con-Presa-Elettrica-e-Caricabatteria-da-Auto-Pocken-Bianco,"Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken Bianco","Informatica Elettronica,Informatica,Elettronica,Batterie, Caricatori, Adattatori,Batterie,,Caricatori,,Adattatori,Colore Bianco,Bianco,","Non uscire di casa senza la doppia porta USB con presa elettrica e caricabatteria da auto Pocken per auto! Puoi connetterla in auto o alla rete elettrica per ricaricare i dispositivi mobili, poichè include sia un adattatore per auto che una spina",http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_0002.jpg,,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_0002.jpg,,,,,,"2015-05-14 07:58:09",,,,,,,,,,,,,,,,,"GTIN=4899888106944",161,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_00.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_08.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_04.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_004.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_0004.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_03.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_0003.jpg","GTIN=4899888106944",,,,,,,,,, +BB-I4115045,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Batterie, Caricatori, Adattatori",base,"Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken Nero","<a id=""maggiorni_informazioni"" title=""Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken ""><p>Non</a> uscire di casa senza la <strong>doppia porta USB con presa elettrica e caricabatteria da auto Pocken</strong> per auto! Puoi connetterla in auto o alla rete elettrica per ricaricare i dispositivi mobili, poichè include sia un adattatore per auto che una spina. Questo caricatore con doppio ingresso USB è molto pratico e semplice da usare. Dimensioni approssimative: 6 x 6 x 3,5 cm.</p><p><strong><a href=""http://www.pockenrg.com"">www.pockenrg.com</a>  </strong></p><div><div>Caratteristiche tecniche:</div><ul><li>Ingresso AC: 100-240 V / 0.15 A / 50-60 Hz</li><li>Ingresso DC: 12-24 V / 0.35 A</li><li>Uscita DC: +5  V / 1 A</li></ul></div><p> Dimenzioni per Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken : </br><ul><li>Altezza: 4 Cm</li><li>Larghezza: 6.5 Cm</li><li>Profondita': 6.5 Cm</li><li>Peso: 0.077 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888106678</p>","Non uscire di casa senza la doppia porta USB con presa elettrica e caricabatteria da auto Pocken per auto! Puoi connetterla in auto o alla rete elettrica per ricaricare i dispositivi mobili, poichè include sia un adattatore per auto che una spina.</br><a href=""#maggiorni_informazioni"" title=""Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken ""> Maggiori Informazioni</a>",0.077,1,"Taxable Goods","Catalog, Search",29.99,,,,Doppia-Porta-USB-con-Presa-Elettrica-e-Caricabatteria-da-Auto-Pocken-Nero,"Doppia Porta USB con Presa Elettrica e Caricabatteria da Auto Pocken Nero","Informatica Elettronica,Informatica,Elettronica,Batterie, Caricatori, Adattatori,Batterie,,Caricatori,,Adattatori,Colore Nero,Nero,","Non uscire di casa senza la doppia porta USB con presa elettrica e caricabatteria da auto Pocken per auto! Puoi connetterla in auto o alla rete elettrica per ricaricare i dispositivi mobili, poichè include sia un adattatore per auto che una spina",http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_00.jpg,,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_00.jpg,,,,,,"2015-08-18 12:37:09",,,,,,,,,,,,,,,,,"GTIN=4899888106678",265,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_0002.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_08.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_04.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_004.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_0004.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_03.jpg,http://dropshipping.bigbuy.eu/imgs/cargador_usb_doble_coche_0003.jpg","GTIN=4899888106678",,,,,,,,,, +BB-I3505259,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Cuffie",base,"Auricolari da Corsa GoFit Bianco","<a id=""maggiorni_informazioni"" title=""Auricolari da Corsa GoFit""><p>Fai</a> sport mentre ascolti la musica o parli al telefono indossando questi<strong> auricolari da corsa</strong>! Questi <strong>auricolari sportivi GoFit</strong> sono molto comodi e pratici, si adattano perfettamente al tuo orecchio per una migliore aderenza. Progettate specificamente per gli altleti. Con materiali speciali e ottima qualità sonora. Caratteristiche:</p><ul><li>Suono: stereo</li><li>Connessione audio: cavo con uscita 3,5 mm</li><li>Microfono integrato</li><li>Pulsante di risposta alla chiamata</li><li>Risposta in frequenza: 20-20000 Hz</li><li>Intensità del suono: 93 dB</li><li>Impedenza dello speaker: 32 Ω</li><li>Adatto agli iPhone, smartphone e telefoni cellulari</li></ul><p> Dimenzioni per Auricolari da Corsa GoFit: </br><ul><li>Altezza: 20 Cm</li><li>Larghezza: 9 Cm</li><li>Profondita': 2.7 Cm</li><li>Peso: 0.079 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417204005</p>","Fai sport mentre ascolti la musica o parli al telefono indossando questi auricolari da corsa! Questi auricolari sportivi GoFit sono molto comodi e pratici, si adattano perfettamente al tuo orecchio per una migliore aderenza.</br><a href=""#maggiorni_informazioni"" title=""Auricolari da Corsa GoFit""> Maggiori Informazioni</a>",0.079,1,"Taxable Goods","Catalog, Search",38.9,,,,Auricolari-da-Corsa-GoFit-Bianco,"Auricolari da Corsa GoFit Bianco","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Cuffie,Colore Bianco,Bianco,","Fai sport mentre ascolti la musica o parli al telefono indossando questi auricolari da corsa! Questi auricolari sportivi GoFit sono molto comodi e pratici, si adattano perfettamente al tuo orecchio per una migliore aderenza",http://dropshipping.bigbuy.eu/imgs/I3505223_80641.jpg,,http://dropshipping.bigbuy.eu/imgs/I3505223_80641.jpg,,,,,,"2015-09-23 10:52:34",,,,,,,,,,,,,,,,,"GTIN=8018417204005",21,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I3505223_80644.jpg,http://dropshipping.bigbuy.eu/imgs/I3505223_80643.jpg,http://dropshipping.bigbuy.eu/imgs/I3505223_80642.jpg,http://dropshipping.bigbuy.eu/imgs/I3505223_80640.jpg","GTIN=8018417204005",,,,,,,,,, +BB-I3505260,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Cuffie",base,"Auricolari da Corsa GoFit Arancio","<a id=""maggiorni_informazioni"" title=""Auricolari da Corsa GoFit""><p>Fai</a> sport mentre ascolti la musica o parli al telefono indossando questi<strong> auricolari da corsa</strong>! Questi <strong>auricolari sportivi GoFit</strong> sono molto comodi e pratici, si adattano perfettamente al tuo orecchio per una migliore aderenza. Progettate specificamente per gli altleti. Con materiali speciali e ottima qualità sonora. Caratteristiche:</p><ul><li>Suono: stereo</li><li>Connessione audio: cavo con uscita 3,5 mm</li><li>Microfono integrato</li><li>Pulsante di risposta alla chiamata</li><li>Risposta in frequenza: 20-20000 Hz</li><li>Intensità del suono: 93 dB</li><li>Impedenza dello speaker: 32 Ω</li><li>Adatto agli iPhone, smartphone e telefoni cellulari</li></ul><p> Dimenzioni per Auricolari da Corsa GoFit: </br><ul><li>Altezza: 20 Cm</li><li>Larghezza: 9 Cm</li><li>Profondita': 2.7 Cm</li><li>Peso: 0.079 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417209512</p>","Fai sport mentre ascolti la musica o parli al telefono indossando questi auricolari da corsa! Questi auricolari sportivi GoFit sono molto comodi e pratici, si adattano perfettamente al tuo orecchio per una migliore aderenza.</br><a href=""#maggiorni_informazioni"" title=""Auricolari da Corsa GoFit""> Maggiori Informazioni</a>",0.079,1,"Taxable Goods","Catalog, Search",38.9,,,,Auricolari-da-Corsa-GoFit-Arancio,"Auricolari da Corsa GoFit Arancio","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Cuffie,Colore Arancio,Arancio,","Fai sport mentre ascolti la musica o parli al telefono indossando questi auricolari da corsa! Questi auricolari sportivi GoFit sono molto comodi e pratici, si adattano perfettamente al tuo orecchio per una migliore aderenza",http://dropshipping.bigbuy.eu/imgs/I3505223_80644.jpg,,http://dropshipping.bigbuy.eu/imgs/I3505223_80644.jpg,,,,,,"2015-09-23 10:52:34",,,,,,,,,,,,,,,,,"GTIN=8018417209512",43,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I3505223_80641.jpg,http://dropshipping.bigbuy.eu/imgs/I3505223_80643.jpg,http://dropshipping.bigbuy.eu/imgs/I3505223_80642.jpg,http://dropshipping.bigbuy.eu/imgs/I3505223_80640.jpg","GTIN=8018417209512",,,,,,,,,, +BB-I3505248,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Casse",base,"Altoparlante Bluetooth Portatile AudioSonic SK1511 Azzurro","<a id=""maggiorni_informazioni"" title=""Altoparlante Bluetooth Portatile AudioSonic""><p>Se</a> adori la musica e la tecnologia, l'<strong>altoparlante Bluetooth portatile AudioSonic</strong> è l'ideale per te! Riesci ad immaginare di indossare questo <strong>altoparlante</strong> intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smartphone?<strong> </strong>Con questo prodotto versatile è possibile! Lunghezza della corda in silicone: circa 40 cm. Dimensioni: circa 5,5 x 7 x 1 cm. Raggio del bluetooth: circa 10 m.</p><p>Caratteristiche:</p><ul><li>Batteria ricaricabile Li-ion</li><li>Vita della batteria: 4 ore</li><li>Batteria 300 mAh</li><li>Microfono incorporato</li><li>Controllo a mani libere</li><li>Pulsanti di controllo</li><li>Porta micro USB porta: 5 V</li><li>Porta input Aux</li><li>Potenza: 3 W</li></ul><p>Include:</p><ul><li>Cavo USB + micro USB</li><li>Cavo dual jack da 3,5 mm</li></ul><p> Dimenzioni per Altoparlante Bluetooth Portatile AudioSonic: </br><ul><li>Altezza: 13 Cm</li><li>Larghezza: 8 Cm</li><li>Profondita': 4.6 Cm</li><li>Peso: 0.143 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016015112</p>","Se adori la musica e la tecnologia, l'altoparlante Bluetooth portatile AudioSonic è l'ideale per te! Riesci ad immaginare di indossare questo altoparlante intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smartphone? Con questo prodotto versatile è possibile! Lunghezza della corda in silicone: circa 40 cm.</br><a href=""#maggiorni_informazioni"" title=""Altoparlante Bluetooth Portatile AudioSonic""> Maggiori Informazioni</a>",0.143,1,"Taxable Goods","Catalog, Search",33.35,,,,Altoparlante-Bluetooth-Portatile-AudioSonic-SK1511 Azzurro,"Altoparlante Bluetooth Portatile AudioSonic SK1511 Azzurro","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Casse,Referenza e Colore SK1511 Azzurro,SK1511 Azzurro,","Se adori la musica e la tecnologia, l'altoparlante Bluetooth portatile AudioSonic è l'ideale per te! Riesci ad immaginare di indossare questo altoparlante intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smart",http://dropshipping.bigbuy.eu/imgs/altavoz_audiosonic_SK-1539_00.jpg,,http://dropshipping.bigbuy.eu/imgs/altavoz_audiosonic_SK-1539_00.jpg,,,,,,"2015-12-21 11:15:29",,,,,,,,,,,,,,,,,"GTIN=8713016015112",8,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_01.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz-cordón-SK-1511.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_004.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_02.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_0004.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_00.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_04.jpg","GTIN=8713016015112",,,,,,,,,, +BB-I3505249,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Casse",base,"Altoparlante Bluetooth Portatile AudioSonic SK1513 Rosa","<a id=""maggiorni_informazioni"" title=""Altoparlante Bluetooth Portatile AudioSonic""><p>Se</a> adori la musica e la tecnologia, l'<strong>altoparlante Bluetooth portatile AudioSonic</strong> è l'ideale per te! Riesci ad immaginare di indossare questo <strong>altoparlante</strong> intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smartphone?<strong> </strong>Con questo prodotto versatile è possibile! Lunghezza della corda in silicone: circa 40 cm. Dimensioni: circa 5,5 x 7 x 1 cm. Raggio del bluetooth: circa 10 m.</p><p>Caratteristiche:</p><ul><li>Batteria ricaricabile Li-ion</li><li>Vita della batteria: 4 ore</li><li>Batteria 300 mAh</li><li>Microfono incorporato</li><li>Controllo a mani libere</li><li>Pulsanti di controllo</li><li>Porta micro USB porta: 5 V</li><li>Porta input Aux</li><li>Potenza: 3 W</li></ul><p>Include:</p><ul><li>Cavo USB + micro USB</li><li>Cavo dual jack da 3,5 mm</li></ul><p> Dimenzioni per Altoparlante Bluetooth Portatile AudioSonic: </br><ul><li>Altezza: 13 Cm</li><li>Larghezza: 8 Cm</li><li>Profondita': 4.6 Cm</li><li>Peso: 0.143 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016000996</p>","Se adori la musica e la tecnologia, l'altoparlante Bluetooth portatile AudioSonic è l'ideale per te! Riesci ad immaginare di indossare questo altoparlante intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smartphone? Con questo prodotto versatile è possibile! Lunghezza della corda in silicone: circa 40 cm.</br><a href=""#maggiorni_informazioni"" title=""Altoparlante Bluetooth Portatile AudioSonic""> Maggiori Informazioni</a>",0.143,1,"Taxable Goods","Catalog, Search",33.35,,,,Altoparlante-Bluetooth-Portatile-AudioSonic-SK1513 Rosa,"Altoparlante Bluetooth Portatile AudioSonic SK1513 Rosa","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Casse,Referenza e Colore SK1513 Rosa,SK1513 Rosa,","Se adori la musica e la tecnologia, l'altoparlante Bluetooth portatile AudioSonic è l'ideale per te! Riesci ad immaginare di indossare questo altoparlante intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smart",http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_01.jpg,,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_01.jpg,,,,,,"2015-06-01 09:01:55",,,,,,,,,,,,,,,,,"GTIN=8713016000996",16,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/altavoz_audiosonic_SK-1539_00.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz-cordón-SK-1511.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_004.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_02.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_0004.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_00.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_04.jpg","GTIN=8713016000996",,,,,,,,,, +BB-I3505250,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Casse",base,"Altoparlante Bluetooth Portatile AudioSonic SK1512 Verde","<a id=""maggiorni_informazioni"" title=""Altoparlante Bluetooth Portatile AudioSonic""><p>Se</a> adori la musica e la tecnologia, l'<strong>altoparlante Bluetooth portatile AudioSonic</strong> è l'ideale per te! Riesci ad immaginare di indossare questo <strong>altoparlante</strong> intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smartphone?<strong> </strong>Con questo prodotto versatile è possibile! Lunghezza della corda in silicone: circa 40 cm. Dimensioni: circa 5,5 x 7 x 1 cm. Raggio del bluetooth: circa 10 m.</p><p>Caratteristiche:</p><ul><li>Batteria ricaricabile Li-ion</li><li>Vita della batteria: 4 ore</li><li>Batteria 300 mAh</li><li>Microfono incorporato</li><li>Controllo a mani libere</li><li>Pulsanti di controllo</li><li>Porta micro USB porta: 5 V</li><li>Porta input Aux</li><li>Potenza: 3 W</li></ul><p>Include:</p><ul><li>Cavo USB + micro USB</li><li>Cavo dual jack da 3,5 mm</li></ul><p> Dimenzioni per Altoparlante Bluetooth Portatile AudioSonic: </br><ul><li>Altezza: 13 Cm</li><li>Larghezza: 8 Cm</li><li>Profondita': 4.6 Cm</li><li>Peso: 0.143 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016000972</p>","Se adori la musica e la tecnologia, l'altoparlante Bluetooth portatile AudioSonic è l'ideale per te! Riesci ad immaginare di indossare questo altoparlante intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smartphone? Con questo prodotto versatile è possibile! Lunghezza della corda in silicone: circa 40 cm.</br><a href=""#maggiorni_informazioni"" title=""Altoparlante Bluetooth Portatile AudioSonic""> Maggiori Informazioni</a>",0.143,1,"Taxable Goods","Catalog, Search",33.35,,,,Altoparlante-Bluetooth-Portatile-AudioSonic-SK1512 Verde,"Altoparlante Bluetooth Portatile AudioSonic SK1512 Verde","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Casse,Referenza e Colore SK1512 Verde,SK1512 Verde,","Se adori la musica e la tecnologia, l'altoparlante Bluetooth portatile AudioSonic è l'ideale per te! Riesci ad immaginare di indossare questo altoparlante intorno al collo mentre ascolti la tua musica preferita e rispondi alle chiamate del tuo smart",http://dropshipping.bigbuy.eu/imgs/altavoz-cordón-SK-1511.jpg,,http://dropshipping.bigbuy.eu/imgs/altavoz-cordón-SK-1511.jpg,,,,,,"2016-02-01 10:38:01",,,,,,,,,,,,,,,,,"GTIN=8713016000972",22,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/altavoz_audiosonic_SK-1539_00.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_01.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_004.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_02.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_0004.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_00.jpg,http://dropshipping.bigbuy.eu/imgs/altavoz_SK-1511_04.jpg","GTIN=8713016000972",,,,,,,,,, +BB-I3505262,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Cuffie",base,"Auricolari da Corsa GoFit","<a id=""maggiorni_informazioni"" title=""Auricolari da Corsa GoFit""><p>Ora,</a> ascoltare la musica o fare una chiamata non saranno più scuse valide per non allenarsi, grazie agli <strong>auricolari da corsa GoFit</strong>! Sono davvero comodi e pratici <strong>auricolari </strong>che si adattano completamente all'orecchio, Speciale <strong>design Europeo</strong> per l'utilizzo in allenamento. Materiali e suono di alta qualità. Include una piccola custodia in tessuto per conservare gli auricolari.</p><p>Caratteristiche:</p><ul><li>Flessibili e resistenti all'acqua</li><li>Suono: stereo</li><li>Connessione audio: cavo jack 3,5 mm</li><li>Microfono incorporato</li><li>Pulsante di risposta e termine chiamata</li><li>Risposta di frequenza: 20-20000 Hz</li><li>Livello sonoro: 93 dB</li><li>Impedenza altoparlante: 32 Ω</li><li>Adatti all'uso con iPhone, smartphone e altri cellulari</li></ul><p> Dimenzioni per Auricolari da Corsa GoFit: </br><ul><li>Altezza: 20.5 Cm</li><li>Larghezza: 9 Cm</li><li>Profondita': 4 Cm</li><li>Peso: 0.102 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417208119</p>","Ora, ascoltare la musica o fare una chiamata non saranno più scuse valide per non allenarsi, grazie agli auricolari da corsa GoFit! Sono davvero comodi e pratici auricolari che si adattano completamente all'orecchio, Speciale design Europeo per l'utilizzo in allenamento.</br><a href=""#maggiorni_informazioni"" title=""Auricolari da Corsa GoFit""> Maggiori Informazioni</a>",0.102,1,"Taxable Goods","Catalog, Search",49.9,,,,Auricolari-da-Corsa-GoFit,"Auricolari da Corsa GoFit","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Cuffie,","Ora, ascoltare la musica o fare una chiamata non saranno più scuse valide per non allenarsi, grazie agli auricolari da corsa GoFit! Sono davvero comodi e pratici auricolari che si adattano completamente all'orecchio, Speciale design Europeo per l'ut",http://dropshipping.bigbuy.eu/imgs/I3505262_80647.jpg,,http://dropshipping.bigbuy.eu/imgs/I3505262_80647.jpg,,,,,,"2016-09-07 15:19:36",,,,,,,,,,,,,,,,,"GTIN=8018417208119",46,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I3505262_80646.jpg,http://dropshipping.bigbuy.eu/imgs/I3505262_80645.jpg","GTIN=8018417208119",,,,,,,,,, +BB-G0500185,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Cuffie",base,"Fascia Sportiva con Auricolari GoFit Verde","<a id=""maggiorni_informazioni"" title=""Fascia Sportiva con Auricolari GoFit""><p>Se</a> adori gli sport e ti piace tenerti informato con gli ultimi <strong>accessori sportivi</strong>, non puoi perderti questa ottima <strong>fascia sportiva con auricolari GoFit</strong>. Con questa fascia per la testa, potrai ascoltare i tuoi brani musicali preferiti mentre fai jogging, inoltre potrai rispondere alle chiamate, semplicemente premendo il pulsante di risposta e chiusura chiamate incorporato nella doppia connessione, con cavo audio jack da 3,5 mm (lunghezza: circa 1 m). Altoparlanti rimovibili per permetterti di lavare la fascia. Ampiezza: circa 9,5 cm. Diametro: circa 27 cm. Esterno 100% poliestere. Interno in microfibra polare.<br /><br />Caratteristiche:</p><ul><li>Sensibilità: 5 dB</li><li>Impedenza altoparlante: 32 Ω</li><li>Frequenza: 20 Hz-20 kHz</li><li>Adatto a smartphone e altri telefoni mobili</li></ul><p> Dimenzioni per Fascia Sportiva con Auricolari GoFit: </br><ul><li>Altezza: 20 Cm</li><li>Larghezza: 9 Cm</li><li>Profondita': 4.5 Cm</li><li>Peso: 0.102 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417209635</p>","Se adori gli sport e ti piace tenerti informato con gli ultimi accessori sportivi, non puoi perderti questa ottima fascia sportiva con auricolari GoFit.</br><a href=""#maggiorni_informazioni"" title=""Fascia Sportiva con Auricolari GoFit""> Maggiori Informazioni</a>",0.102,1,"Taxable Goods","Catalog, Search",49.99,,,,Fascia-Sportiva-con-Auricolari-GoFit-Verde,"Fascia Sportiva con Auricolari GoFit Verde","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Cuffie,Colore Verde,Verde,","Se adori gli sport e ti piace tenerti informato con gli ultimi accessori sportivi, non puoi perderti questa ottima fascia sportiva con auricolari GoFit",http://dropshipping.bigbuy.eu/imgs/G0500184_81112.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500184_81112.jpg,,,,,,"2015-09-28 07:07:26",,,,,,,,,,,,,,,,,"GTIN=8018417209635",11,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500184_81118.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81117.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81116.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81115.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81114.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81113.jpg","GTIN=8018417209635",,,,,,,,,, +BB-G0500186,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Cuffie",base,"Fascia Sportiva con Auricolari GoFit Arancio","<a id=""maggiorni_informazioni"" title=""Fascia Sportiva con Auricolari GoFit""><p>Se</a> adori gli sport e ti piace tenerti informato con gli ultimi <strong>accessori sportivi</strong>, non puoi perderti questa ottima <strong>fascia sportiva con auricolari GoFit</strong>. Con questa fascia per la testa, potrai ascoltare i tuoi brani musicali preferiti mentre fai jogging, inoltre potrai rispondere alle chiamate, semplicemente premendo il pulsante di risposta e chiusura chiamate incorporato nella doppia connessione, con cavo audio jack da 3,5 mm (lunghezza: circa 1 m). Altoparlanti rimovibili per permetterti di lavare la fascia. Ampiezza: circa 9,5 cm. Diametro: circa 27 cm. Esterno 100% poliestere. Interno in microfibra polare.<br /><br />Caratteristiche:</p><ul><li>Sensibilità: 5 dB</li><li>Impedenza altoparlante: 32 Ω</li><li>Frequenza: 20 Hz-20 kHz</li><li>Adatto a smartphone e altri telefoni mobili</li></ul><p> Dimenzioni per Fascia Sportiva con Auricolari GoFit: </br><ul><li>Altezza: 20 Cm</li><li>Larghezza: 9 Cm</li><li>Profondita': 4.5 Cm</li><li>Peso: 0.102 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417209505</p>","Se adori gli sport e ti piace tenerti informato con gli ultimi accessori sportivi, non puoi perderti questa ottima fascia sportiva con auricolari GoFit.</br><a href=""#maggiorni_informazioni"" title=""Fascia Sportiva con Auricolari GoFit""> Maggiori Informazioni</a>",0.102,1,"Taxable Goods","Catalog, Search",49.99,,,,Fascia-Sportiva-con-Auricolari-GoFit-Arancio,"Fascia Sportiva con Auricolari GoFit Arancio","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Cuffie,Colore Arancio,Arancio,","Se adori gli sport e ti piace tenerti informato con gli ultimi accessori sportivi, non puoi perderti questa ottima fascia sportiva con auricolari GoFit",http://dropshipping.bigbuy.eu/imgs/G0500184_81118.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500184_81118.jpg,,,,,,"2015-09-28 07:07:51",,,,,,,,,,,,,,,,,"GTIN=8018417209505",32,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500184_81112.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81117.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81116.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81115.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81114.jpg,http://dropshipping.bigbuy.eu/imgs/G0500184_81113.jpg","GTIN=8018417209505",,,,,,,,,, +BB-I3505265,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Casse",base,"Altoparlante Sportivo Bluetooth GoFit","<a id=""maggiorni_informazioni"" title=""Altoparlante Sportivo Bluetooth GoFit""><p>Sei</a> un amante dello sport e adori fare escursioni in montagna? Ti piace la musica? Allora questo prodotto ti starà a pennello! Questo favoloso <strong>altoparlante sportivo Bluetooth GoFit</strong> ti accompagnerà dove vuoi, permettendoti di ascoltare la tua musica preferita durante le tue uscite, così come rispondere alle chiamate attraverso la funzione Bluetooth. Resistente all'acqua. Include un cavo di ricarica USB. L'altoparlante ha una clip per inserirlo su una cintura e una banda elastica per indossarlo al polso, nello zaino, ecc. Misure (diametro x altezza) circa: 8,5 cm x 3 cm. Peso: circa 159 g.</p><p>Caratteristiche:</p><ul><li>Protezione impermeabile: IP4</li><li>Bluetooth 2.0 + EDR: distanza fino a circa 10 m</li><li>Funzione mani libere</li><li>Permette la ricezione di chiamate e terminarle</li><li>Pulsanti di pausa, avanzamento e indietro</li><li>Tempo di riproduzione: circa 2,5 ore</li><li>Frequenza: 90 Hz-20 KHz</li><li>Uscita altoparlante: 5 W</li><li>Connessione per audio jack<em> </em>3,5 mm</li></ul><p> </p><p> Dimenzioni per Altoparlante Sportivo Bluetooth GoFit: </br><ul><li>Altezza: 20 Cm</li><li>Larghezza: 6 Cm</li><li>Profondita': 9.5 Cm</li><li>Peso: 0.278 Kg</li></ul></p><p>Codice Prodotto (EAN): 8018417207747</p>","Sei un amante dello sport e adori fare escursioni in montagna? Ti piace la musica? Allora questo prodotto ti starà a pennello! Questo favoloso altoparlante sportivo Bluetooth GoFit ti accompagnerà dove vuoi, permettendoti di ascoltare la tua musica preferita durante le tue uscite, così come rispondere alle chiamate attraverso la funzione Bluetooth.</br><a href=""#maggiorni_informazioni"" title=""Altoparlante Sportivo Bluetooth GoFit""> Maggiori Informazioni</a>",0.278,1,"Taxable Goods","Catalog, Search",89.9,,,,Altoparlante-Sportivo-Bluetooth-GoFit,"Altoparlante Sportivo Bluetooth GoFit","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Casse,","Sei un amante dello sport e adori fare escursioni in montagna? Ti piace la musica? Allora questo prodotto ti starà a pennello! Questo favoloso altoparlante sportivo Bluetooth GoFit ti accompagnerà dove vuoi, permettendoti di ascoltare la tua musica ",http://dropshipping.bigbuy.eu/imgs/I3505265_81327.jpg,,http://dropshipping.bigbuy.eu/imgs/I3505265_81327.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8018417207747",10,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I3505265_81169.jpg,http://dropshipping.bigbuy.eu/imgs/I3505265_81168.jpg,http://dropshipping.bigbuy.eu/imgs/I3505265_81167.jpg","GTIN=8018417207747",,,,,,,,,, +BB-H1000173,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Scooter Elettrici",base,"Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid Azzurro","<a id=""maggiorni_informazioni"" title=""Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid""><p>Dimentica</a> i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spostarsi che chiunque noterà. Metti alla prova il tuo equilibrio e divertiti!</p><p><br /><strong><a href=""http://www.roverdroid.com/"">www.roverdroid.com</a></strong></p><p>Caratteristiche:</p><ul><li>Batteria a litio: 160 Wh</li><li>Pulsante on/off</li><li>Indicatore luminoso del livello di carica sull'asse centrale</li><li>Illuminazione di segnaletica anteriore LED</li><li>Zona d'appoggio in gomma resistente e antiscivolo</li><li>Velocità massima: 10 km/h circa</li><li>Autonomia con carica completa: da 17 a 20 km circa</li><li>Peso: 10 kg circa</li><li>Tempo di carica: 3 ore circa</li><li>Peso massimo supportato: 100 kg</li><li>Dimensioni: 58 x 18 x 18 cm circa</li><li>Diametro delle ruote: 7""</li><li>Borsa di trasporto e caricabatterie inclusi (CA: 100-240 V, 50-60 Hz, 1,2 A / DC: 36 V, 2 A)</li></ul><p> Dimenzioni per Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid: </br><ul><li>Altezza: 22.5 Cm</li><li>Larghezza: 64 Cm</li><li>Profondita': 24 Cm</li><li>Peso: 11.85 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888109204</p>","Dimentica i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spostarsi che chiunque noterà.</br><a href=""#maggiorni_informazioni"" title=""Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid""> Maggiori Informazioni</a>",11.85,1,"Taxable Goods","Catalog, Search",599,,,,Mini-Scooter-Elettrico-di-Auto-Equilibrio-(2-ruote)-Rover-Droid-Azzurro,"Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid Azzurro","Informatica Elettronica,Informatica,Elettronica,Scooter Elettrici,Scooter,Elettrici,Colore Azzurro,Azzurro,","Dimentica i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spos",http://dropshipping.bigbuy.eu/imgs/H1000172_90268.jpg,,http://dropshipping.bigbuy.eu/imgs/H1000172_90268.jpg,,,,,,"2016-02-25 15:50:21",,,,,,,,,,,,,,,,,"GTIN=4899888109204",60,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/H1000172_90282.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_90270.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87995.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87979.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87978.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87976.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87975.jpg","GTIN=4899888109204",,,,,,,,,, +BB-H1000174,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Scooter Elettrici",base,"Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid Nero","<a id=""maggiorni_informazioni"" title=""Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid""><p>Dimentica</a> i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spostarsi che chiunque noterà. Metti alla prova il tuo equilibrio e divertiti!</p><p><br /><strong><a href=""http://www.roverdroid.com/"">www.roverdroid.com</a></strong></p><p>Caratteristiche:</p><ul><li>Batteria a litio: 160 Wh</li><li>Pulsante on/off</li><li>Indicatore luminoso del livello di carica sull'asse centrale</li><li>Illuminazione di segnaletica anteriore LED</li><li>Zona d'appoggio in gomma resistente e antiscivolo</li><li>Velocità massima: 10 km/h circa</li><li>Autonomia con carica completa: da 17 a 20 km circa</li><li>Peso: 10 kg circa</li><li>Tempo di carica: 3 ore circa</li><li>Peso massimo supportato: 100 kg</li><li>Dimensioni: 58 x 18 x 18 cm circa</li><li>Diametro delle ruote: 7""</li><li>Borsa di trasporto e caricabatterie inclusi (CA: 100-240 V, 50-60 Hz, 1,2 A / DC: 36 V, 2 A)</li></ul><p> Dimenzioni per Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid: </br><ul><li>Altezza: 22.5 Cm</li><li>Larghezza: 64 Cm</li><li>Profondita': 24 Cm</li><li>Peso: 11.85 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888109273</p>","Dimentica i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spostarsi che chiunque noterà.</br><a href=""#maggiorni_informazioni"" title=""Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid""> Maggiori Informazioni</a>",11.85,1,"Taxable Goods","Catalog, Search",599,,,,Mini-Scooter-Elettrico-di-Auto-Equilibrio-(2-ruote)-Rover-Droid-Nero,"Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid Nero","Informatica Elettronica,Informatica,Elettronica,Scooter Elettrici,Scooter,Elettrici,Colore Nero,Nero,","Dimentica i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spos",http://dropshipping.bigbuy.eu/imgs/H1000172_90282.jpg,,http://dropshipping.bigbuy.eu/imgs/H1000172_90282.jpg,,,,,,"2016-02-25 15:50:21",,,,,,,,,,,,,,,,,"GTIN=4899888109273",131,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/H1000172_90268.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_90270.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87995.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87979.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87978.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87976.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87975.jpg","GTIN=4899888109273",,,,,,,,,, +BB-H1000182,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Scooter Elettrici",base,"Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid Grafitti","<a id=""maggiorni_informazioni"" title=""Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid""><p>Dimentica</a> i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spostarsi che chiunque noterà. Metti alla prova il tuo equilibrio e divertiti!</p><p><br /><strong><a href=""http://www.roverdroid.com/"">www.roverdroid.com</a></strong></p><p>Caratteristiche:</p><ul><li>Batteria a litio: 160 Wh</li><li>Pulsante on/off</li><li>Indicatore luminoso del livello di carica sull'asse centrale</li><li>Illuminazione di segnaletica anteriore LED</li><li>Zona d'appoggio in gomma resistente e antiscivolo</li><li>Velocità massima: 10 km/h circa</li><li>Autonomia con carica completa: da 17 a 20 km circa</li><li>Peso: 10 kg circa</li><li>Tempo di carica: 3 ore circa</li><li>Peso massimo supportato: 100 kg</li><li>Dimensioni: 58 x 18 x 18 cm circa</li><li>Diametro delle ruote: 7""</li><li>Borsa di trasporto e caricabatterie inclusi (CA: 100-240 V, 50-60 Hz, 1,2 A / DC: 36 V, 2 A)</li></ul><p> Dimenzioni per Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid: </br><ul><li>Altezza: 22.5 Cm</li><li>Larghezza: 64 Cm</li><li>Profondita': 24 Cm</li><li>Peso: 11.85 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888109747</p>","Dimentica i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spostarsi che chiunque noterà.</br><a href=""#maggiorni_informazioni"" title=""Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid""> Maggiori Informazioni</a>",11.85,1,"Taxable Goods","Catalog, Search",599,,,,Mini-Scooter-Elettrico-di-Auto-Equilibrio-(2-ruote)-Rover-Droid-Grafitti,"Mini Scooter Elettrico di Auto Equilibrio (2 ruote) Rover Droid Grafitti","Informatica Elettronica,Informatica,Elettronica,Scooter Elettrici,Scooter,Elettrici,Colore Grafitti,Grafitti,","Dimentica i noiosi trasporti convenzionali e diventa il re delle strade con il mini scooter elettrico di auto equilibrio (2 ruote) Rover Droid! Questo pratico, intelligente ed originale veicolo motorizzato costituisce un modo rapido e comodo di spos",http://dropshipping.bigbuy.eu/imgs/H1000172_90270.jpg,,http://dropshipping.bigbuy.eu/imgs/H1000172_90270.jpg,,,,,,"2016-02-25 16:33:57",,,,,,,,,,,,,,,,,"GTIN=4899888109747",92,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/H1000172_90268.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_90282.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87995.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87979.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87978.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87976.jpg,http://dropshipping.bigbuy.eu/imgs/H1000172_87975.jpg","GTIN=4899888109747",,,,,,,,,, +BB-I2500322,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Orologi e Sveglie,Default Category/Informatica Elettronica/Orologi e Sveglie/Smartwatch",base,"Orologio Intelligente Smartwatch BT110 con Audio Nero","<a id=""maggiorni_informazioni"" title=""Orologio Intelligente Smartwatch BT110 con Audio""><p>Sfoggia</a> il tuo <strong>orologio intelligente Smartwatch BT110 con audio</strong>! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usarlo come cronometro, barometro, altimetro e podometro. Inoltre, questo orologio dispone di varie funzioni autonome: allarme, calendario, calcolatrice, controllo del sonno...</p><p><a href=""http://www.bitblin.com/"" target=""_blank""><strong>www.bitblin.com</strong></a><br /> <br />Caratteristiche:</p><ul><li>Schermo touch</li><li>Risoluzione: 128 x 128 pixel</li><li>Batteria a litio: 3,7 V / 230 mA</li><li>Durata appross. in attesa: 150 h</li><li>Durata appross. in conversazione: 3 h</li><li>Connessione micro USB</li><li>Bluetooth 3.0</li><li>Vibrazione</li><li>Dimensioni appross. della sfera: 4 x 4,5 x 1 cm</li><li>Cavo USB da micro USB incluso</li><li>Il menu è disponibile in spagnolo, inglese, francese, danese, polacco, portoghese, italiano, tedesco, turco, russo e svedese.</li><li>Compatibile con smartphones Android</li></ul><p> </p><p>Con Smartphone Android da 4.0 a 5.0 è possibile ricevere notifiche di SMS, e-mails, WhatsApp e social network (scaricando l'applicazione indicata nella pagina web del prodotto).</p><p> Dimenzioni per Orologio Intelligente Smartwatch BT110 con Audio: </br><ul><li>Altezza: 11 Cm</li><li>Larghezza: 8 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.145 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888109242</p>","Sfoggia il tuo orologio intelligente Smartwatch BT110 con audio! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usarlo come cronometro, barometro, altimetro e podometro.</br><a href=""#maggiorni_informazioni"" title=""Orologio Intelligente Smartwatch BT110 con Audio""> Maggiori Informazioni</a>",0.145,1,"Taxable Goods","Catalog, Search",94.9,,,,Orologio-Intelligente-Smartwatch-BT110-con-Audio-Nero,"Orologio Intelligente Smartwatch BT110 con Audio Nero","Informatica Elettronica,Informatica,Elettronica,Orologi e Sveglie,Orologi,Sveglie,Smartwatch,Colore Nero,Nero,","Sfoggia il tuo orologio intelligente Smartwatch BT110 con audio! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usa",http://dropshipping.bigbuy.eu/imgs/I2500321_84125.jpg,,http://dropshipping.bigbuy.eu/imgs/I2500321_84125.jpg,,,,,,"2016-02-11 08:36:39",,,,,,,,,,,,,,,,,"GTIN=4899888109242",2,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I2500321_101196.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101195.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101194.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101193.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84129.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84128.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84127.jpg","GTIN=4899888109242",,,,,,,,,, +BB-I2500323,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Orologi e Sveglie,Default Category/Informatica Elettronica/Orologi e Sveglie/Smartwatch",base,"Orologio Intelligente Smartwatch BT110 con Audio Bianco","<a id=""maggiorni_informazioni"" title=""Orologio Intelligente Smartwatch BT110 con Audio""><p>Sfoggia</a> il tuo <strong>orologio intelligente Smartwatch BT110 con audio</strong>! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usarlo come cronometro, barometro, altimetro e podometro. Inoltre, questo orologio dispone di varie funzioni autonome: allarme, calendario, calcolatrice, controllo del sonno...</p><p><a href=""http://www.bitblin.com/"" target=""_blank""><strong>www.bitblin.com</strong></a><br /> <br />Caratteristiche:</p><ul><li>Schermo touch</li><li>Risoluzione: 128 x 128 pixel</li><li>Batteria a litio: 3,7 V / 230 mA</li><li>Durata appross. in attesa: 150 h</li><li>Durata appross. in conversazione: 3 h</li><li>Connessione micro USB</li><li>Bluetooth 3.0</li><li>Vibrazione</li><li>Dimensioni appross. della sfera: 4 x 4,5 x 1 cm</li><li>Cavo USB da micro USB incluso</li><li>Il menu è disponibile in spagnolo, inglese, francese, danese, polacco, portoghese, italiano, tedesco, turco, russo e svedese.</li><li>Compatibile con smartphones Android</li></ul><p> </p><p>Con Smartphone Android da 4.0 a 5.0 è possibile ricevere notifiche di SMS, e-mails, WhatsApp e social network (scaricando l'applicazione indicata nella pagina web del prodotto).</p><p> Dimenzioni per Orologio Intelligente Smartwatch BT110 con Audio: </br><ul><li>Altezza: 11 Cm</li><li>Larghezza: 8 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.145 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888109259</p>","Sfoggia il tuo orologio intelligente Smartwatch BT110 con audio! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usarlo come cronometro, barometro, altimetro e podometro.</br><a href=""#maggiorni_informazioni"" title=""Orologio Intelligente Smartwatch BT110 con Audio""> Maggiori Informazioni</a>",0.145,1,"Taxable Goods","Catalog, Search",94.9,,,,Orologio-Intelligente-Smartwatch-BT110-con-Audio-Bianco,"Orologio Intelligente Smartwatch BT110 con Audio Bianco","Informatica Elettronica,Informatica,Elettronica,Orologi e Sveglie,Orologi,Sveglie,Smartwatch,Colore Bianco,Bianco,","Sfoggia il tuo orologio intelligente Smartwatch BT110 con audio! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usa",http://dropshipping.bigbuy.eu/imgs/I2500321_101196.jpg,,http://dropshipping.bigbuy.eu/imgs/I2500321_101196.jpg,,,,,,"2016-02-11 08:36:39",,,,,,,,,,,,,,,,,"GTIN=4899888109259",2,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I2500321_84125.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101195.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101194.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101193.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84129.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84128.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84127.jpg","GTIN=4899888109259",,,,,,,,,, +BB-I2500324,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Orologi e Sveglie,Default Category/Informatica Elettronica/Orologi e Sveglie/Smartwatch",base,"Orologio Intelligente Smartwatch BT110 con Audio Rosso","<a id=""maggiorni_informazioni"" title=""Orologio Intelligente Smartwatch BT110 con Audio""><p>Sfoggia</a> il tuo <strong>orologio intelligente Smartwatch BT110 con audio</strong>! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usarlo come cronometro, barometro, altimetro e podometro. Inoltre, questo orologio dispone di varie funzioni autonome: allarme, calendario, calcolatrice, controllo del sonno...</p><p><a href=""http://www.bitblin.com/"" target=""_blank""><strong>www.bitblin.com</strong></a><br /> <br />Caratteristiche:</p><ul><li>Schermo touch</li><li>Risoluzione: 128 x 128 pixel</li><li>Batteria a litio: 3,7 V / 230 mA</li><li>Durata appross. in attesa: 150 h</li><li>Durata appross. in conversazione: 3 h</li><li>Connessione micro USB</li><li>Bluetooth 3.0</li><li>Vibrazione</li><li>Dimensioni appross. della sfera: 4 x 4,5 x 1 cm</li><li>Cavo USB da micro USB incluso</li><li>Il menu è disponibile in spagnolo, inglese, francese, danese, polacco, portoghese, italiano, tedesco, turco, russo e svedese.</li><li>Compatibile con smartphones Android</li></ul><p> </p><p>Con Smartphone Android da 4.0 a 5.0 è possibile ricevere notifiche di SMS, e-mails, WhatsApp e social network (scaricando l'applicazione indicata nella pagina web del prodotto).</p><p> Dimenzioni per Orologio Intelligente Smartwatch BT110 con Audio: </br><ul><li>Altezza: 11 Cm</li><li>Larghezza: 8 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.145 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888109266</p>","Sfoggia il tuo orologio intelligente Smartwatch BT110 con audio! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usarlo come cronometro, barometro, altimetro e podometro.</br><a href=""#maggiorni_informazioni"" title=""Orologio Intelligente Smartwatch BT110 con Audio""> Maggiori Informazioni</a>",0.145,1,"Taxable Goods","Catalog, Search",94.9,,,,Orologio-Intelligente-Smartwatch-BT110-con-Audio-Rosso,"Orologio Intelligente Smartwatch BT110 con Audio Rosso","Informatica Elettronica,Informatica,Elettronica,Orologi e Sveglie,Orologi,Sveglie,Smartwatch,Colore Rosso,Rosso,","Sfoggia il tuo orologio intelligente Smartwatch BT110 con audio! Sincronizzalo tramite Bluetooth al tuo smartphone e ti permette di realizzare e rispondere alle telefonate, accedere all'agenda e alla cronologia delle chiamate, ascoltare musica e usa",http://dropshipping.bigbuy.eu/imgs/I2500321_101195.jpg,,http://dropshipping.bigbuy.eu/imgs/I2500321_101195.jpg,,,,,,"2016-02-11 08:36:39",,,,,,,,,,,,,,,,,"GTIN=4899888109266",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I2500321_84125.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101196.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101194.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_101193.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84129.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84128.jpg,http://dropshipping.bigbuy.eu/imgs/I2500321_84127.jpg","GTIN=4899888109266",,,,,,,,,, +BB-I4110022,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Smartphone MyWigo UNO 5'' Bianco","<a id=""maggiorni_informazioni"" title=""Smartphone MyWigo UNO 5'' ""><p>Acquista</a> uno dei migliori <strong>telefoni cellulari sbloccati</strong> sul mercato in questo momento, lo <strong>smartphone MyWigo UNO</strong> <strong>5''</strong>! Include caricabatterie e cavetto USB al cavetto micro USB.</p><p>Caratteristiche:</p><ul><li>Schermo Vetro Ricurvo 5'' HD IPS 2.5D </li><li>Batteria a polimeri di litio: 2350 mAh</li><li>Fotocamera anteriore: 5 Mpx</li><li>Telecamera posteriore: 13 Mpx Sony, IMX 214 sensore con autofocus e flash LED</li><li>Processore: MTK6753 Octa Core a 1.3 GHz di 64 Bit</li><li>RAM: 2 GB DDR</li><li>Memoria interna: 32 GB (16 GB + 16 GB micro SD)</li><li>Dual SIM</li><li>Sistema operativo: Android Lollipop 5.1</li><li>Wi-Fi</li><li>Bluetooth 4.0 + HS</li><li>GPS</li><li>2G: GSM a 850/900/1800/1900 MHz</li><li>3G: WCDMA 900/2100 MHz</li><li>Tecnologia 4G (LTE) FDD 800/1800/2100/2600 MHz</li><li>Caricabatterie: AC 110-240 V, DC 5 V, 1000 mA</li><li>Dimensioni: 7 x 14 x 0,8 cm circa</li><li>Dimensioni dello schermo: 6 x 11 cm circa</li><li>Peso: 138 gr circa</li></ul><p> Dimenzioni per Smartphone MyWigo UNO 5'' : </br><ul><li>Altezza: 10.5 Cm</li><li>Larghezza: 18 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.347 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436533839565</p>","Acquista uno dei migliori telefoni cellulari sbloccati sul mercato in questo momento, lo smartphone MyWigo UNO 5''! Include caricabatterie e cavetto USB al cavetto micro USB.</br><a href=""#maggiorni_informazioni"" title=""Smartphone MyWigo UNO 5'' ""> Maggiori Informazioni</a>",0.347,1,"Taxable Goods","Catalog, Search",412.5,,,,Smartphone-MyWigo-UNO-5''-Bianco,"Smartphone MyWigo UNO 5'' Bianco","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Bianco,Bianco,","Acquista uno dei migliori telefoni cellulari sbloccati sul mercato in questo momento, lo smartphone MyWigo UNO 5''! Include caricabatterie e cavetto USB al cavetto micro USB",http://dropshipping.bigbuy.eu/imgs/I4110021_87809.jpg,,http://dropshipping.bigbuy.eu/imgs/I4110021_87809.jpg,,,,,,"2015-12-16 14:44:27",,,,,,,,,,,,,,,,,"GTIN=8436533839565",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I4110021_87813.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87812.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87811.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87810.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87808.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87807.jpg","GTIN=8436533839565",,,,,,,,,, +BB-I4110023,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Smartphone MyWigo UNO 5'' Nero","<a id=""maggiorni_informazioni"" title=""Smartphone MyWigo UNO 5'' ""><p>Acquista</a> uno dei migliori <strong>telefoni cellulari sbloccati</strong> sul mercato in questo momento, lo <strong>smartphone MyWigo UNO</strong> <strong>5''</strong>! Include caricabatterie e cavetto USB al cavetto micro USB.</p><p>Caratteristiche:</p><ul><li>Schermo Vetro Ricurvo 5'' HD IPS 2.5D </li><li>Batteria a polimeri di litio: 2350 mAh</li><li>Fotocamera anteriore: 5 Mpx</li><li>Telecamera posteriore: 13 Mpx Sony, IMX 214 sensore con autofocus e flash LED</li><li>Processore: MTK6753 Octa Core a 1.3 GHz di 64 Bit</li><li>RAM: 2 GB DDR</li><li>Memoria interna: 32 GB (16 GB + 16 GB micro SD)</li><li>Dual SIM</li><li>Sistema operativo: Android Lollipop 5.1</li><li>Wi-Fi</li><li>Bluetooth 4.0 + HS</li><li>GPS</li><li>2G: GSM a 850/900/1800/1900 MHz</li><li>3G: WCDMA 900/2100 MHz</li><li>Tecnologia 4G (LTE) FDD 800/1800/2100/2600 MHz</li><li>Caricabatterie: AC 110-240 V, DC 5 V, 1000 mA</li><li>Dimensioni: 7 x 14 x 0,8 cm circa</li><li>Dimensioni dello schermo: 6 x 11 cm circa</li><li>Peso: 138 gr circa</li></ul><p> Dimenzioni per Smartphone MyWigo UNO 5'' : </br><ul><li>Altezza: 10.5 Cm</li><li>Larghezza: 18 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.347 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436533839558</p>","Acquista uno dei migliori telefoni cellulari sbloccati sul mercato in questo momento, lo smartphone MyWigo UNO 5''! Include caricabatterie e cavetto USB al cavetto micro USB.</br><a href=""#maggiorni_informazioni"" title=""Smartphone MyWigo UNO 5'' ""> Maggiori Informazioni</a>",0.347,1,"Taxable Goods","Catalog, Search",412.5,,,,Smartphone-MyWigo-UNO-5''-Nero,"Smartphone MyWigo UNO 5'' Nero","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Nero,Nero,","Acquista uno dei migliori telefoni cellulari sbloccati sul mercato in questo momento, lo smartphone MyWigo UNO 5''! Include caricabatterie e cavetto USB al cavetto micro USB",http://dropshipping.bigbuy.eu/imgs/I4110021_87813.jpg,,http://dropshipping.bigbuy.eu/imgs/I4110021_87813.jpg,,,,,,"2015-12-16 14:44:27",,,,,,,,,,,,,,,,,"GTIN=8436533839558",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I4110021_87809.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87812.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87811.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87810.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87808.jpg,http://dropshipping.bigbuy.eu/imgs/I4110021_87807.jpg","GTIN=8436533839558",,,,,,,,,, +BB-V1400101,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Telefono Cellulare Thomson Tlink11 Bianco","<a id=""maggiorni_informazioni"" title=""Telefono Cellulare Thomson Tlink11""><p>Se</a> sei alla ricerca di un telefono cellulare user-friendly per le persone anziane o per chi è agli inizi nel mondo della telefonia mobile, il <strong>telefono cellulare </strong><strong>Thomson Tlink11 </strong>è ciò che stai cercando!</p><p>Caratteristiche:</p><ul><li>Telefono cellulare sbloccato</li><li>Schermo: 1.77"" 128 x 160, colori 65 K</li><li>Reti: GSM-EDGE 850/900/1800/1900 MHz</li><li>Dual SIM</li><li>Bluetooth</li><li>MP3</li><li>Radio FM</li><li>Micro USB</li><li>Micro SD (scheda non inclusa)</li><li>Connessione per auricolari (non inclusi)</li><li>Lista contatti: 200 nomi</li><li>Funzione SMS</li><li>Mani-libere</li><li>Batteria Li-ion 600 mAh</li><li>Include: batteria e caricabatterie</li><li>Dimensioni: 4.5 x 11 x 1 cm circa </li></ul><p> Dimenzioni per Telefono Cellulare Thomson Tlink11: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 11 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.167 Kg</li></ul></p><p>Codice Prodotto (EAN): 3527570047688</p>","Se sei alla ricerca di un telefono cellulare user-friendly per le persone anziane o per chi è agli inizi nel mondo della telefonia mobile, il telefono cellulare Thomson Tlink11 è ciò che stai cercando!Caratteristiche:Telefono cellulare sbloccatoSchermo: 1.</br><a href=""#maggiorni_informazioni"" title=""Telefono Cellulare Thomson Tlink11""> Maggiori Informazioni</a>",0.167,1,"Taxable Goods","Catalog, Search",43.5,,,,Telefono-Cellulare-Thomson-Tlink11-Bianco,"Telefono Cellulare Thomson Tlink11 Bianco","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Bianco,Bianco,","Se sei alla ricerca di un telefono cellulare user-friendly per le persone anziane o per chi è agli inizi nel mondo della telefonia mobile, il telefono cellulare Thomson Tlink11 è ciò che stai cercando!Caratteristiche:Telefono cellulare sbloccatoSche",http://dropshipping.bigbuy.eu/imgs/V1400100_91205.jpg,,http://dropshipping.bigbuy.eu/imgs/V1400100_91205.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3527570047688",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1400100_91204.jpg,http://dropshipping.bigbuy.eu/imgs/V1400100_91203.jpg","GTIN=3527570047688",,,,,,,,,, +BB-V1400102,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Telefono Cellulare Thomson Tlink11 Nero","<a id=""maggiorni_informazioni"" title=""Telefono Cellulare Thomson Tlink11""><p>Se</a> sei alla ricerca di un telefono cellulare user-friendly per le persone anziane o per chi è agli inizi nel mondo della telefonia mobile, il <strong>telefono cellulare </strong><strong>Thomson Tlink11 </strong>è ciò che stai cercando!</p><p>Caratteristiche:</p><ul><li>Telefono cellulare sbloccato</li><li>Schermo: 1.77"" 128 x 160, colori 65 K</li><li>Reti: GSM-EDGE 850/900/1800/1900 MHz</li><li>Dual SIM</li><li>Bluetooth</li><li>MP3</li><li>Radio FM</li><li>Micro USB</li><li>Micro SD (scheda non inclusa)</li><li>Connessione per auricolari (non inclusi)</li><li>Lista contatti: 200 nomi</li><li>Funzione SMS</li><li>Mani-libere</li><li>Batteria Li-ion 600 mAh</li><li>Include: batteria e caricabatterie</li><li>Dimensioni: 4.5 x 11 x 1 cm circa </li></ul><p> Dimenzioni per Telefono Cellulare Thomson Tlink11: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 11 Cm</li><li>Profondita': 5 Cm</li><li>Peso: 0.167 Kg</li></ul></p><p>Codice Prodotto (EAN): 3527570047596</p>","Se sei alla ricerca di un telefono cellulare user-friendly per le persone anziane o per chi è agli inizi nel mondo della telefonia mobile, il telefono cellulare Thomson Tlink11 è ciò che stai cercando!Caratteristiche:Telefono cellulare sbloccatoSchermo: 1.</br><a href=""#maggiorni_informazioni"" title=""Telefono Cellulare Thomson Tlink11""> Maggiori Informazioni</a>",0.167,1,"Taxable Goods","Catalog, Search",43.5,,,,Telefono-Cellulare-Thomson-Tlink11-Nero,"Telefono Cellulare Thomson Tlink11 Nero","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Nero,Nero,","Se sei alla ricerca di un telefono cellulare user-friendly per le persone anziane o per chi è agli inizi nel mondo della telefonia mobile, il telefono cellulare Thomson Tlink11 è ciò che stai cercando!Caratteristiche:Telefono cellulare sbloccatoSche",http://dropshipping.bigbuy.eu/imgs/V1400100_91204.jpg,,http://dropshipping.bigbuy.eu/imgs/V1400100_91204.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3527570047596",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1400100_91205.jpg,http://dropshipping.bigbuy.eu/imgs/V1400100_91203.jpg","GTIN=3527570047596",,,,,,,,,, +BB-V1400104,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Telefono Cellulare Thomson Serea51 Bianco","<a id=""maggiorni_informazioni"" title=""Telefono Cellulare Thomson Serea51""><p>In</a> arrivo il <strong>telefono cellulare</strong><strong> Thomson Serea51</strong><strong> </strong>per gli amanti dei cellulari semplici e funzionali, per le persone anziane o per coloro che si avviciano per la prima volta al mondo della telefonia mobile!</p><p>Caratteristiche:</p><ul><li>Cellulare sbloccato</li><li>Schermo: 1,77"" 160 x 128, 65 K colori</li><li>Reti: GSM-EDGE 850/900/1800/1900 MHz</li><li>Tasto per le chiamate d'emergenza</li><li>Tasti di grandi dimensioni</li><li>Luce LED</li><li>Fotocamera VGA</li><li>Bluetooth</li><li>MP3</li><li>Radio FM</li><li>Micro USB</li><li>Micro SD (scheda non inclusa)</li><li>Agenda: 250 voci</li><li>Funzione SMS, MMS</li><li>kit auricolare mani libere</li><li>Batteria Li-ion 800 mAh</li><li>Durata della batteria: 220 h in standby / 5,5 h in conversazione</li><li>Include: batteria, base di ricarica, adattatore di CA, cavo dati USB e auricolari</li><li>Dimensioni (senza base): 5 x 11 x 1,4 cm circa</li></ul><p> Dimenzioni per Telefono Cellulare Thomson Serea51: </br><ul><li>Altezza: 18 Cm</li><li>Larghezza: 11.5 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.288 Kg</li></ul></p><p>Codice Prodotto (EAN): 3527570046964</p>","In arrivo il telefono cellulare Thomson Serea51 per gli amanti dei cellulari semplici e funzionali, per le persone anziane o per coloro che si avviciano per la prima volta al mondo della telefonia mobile!Caratteristiche:Cellulare sbloccatoSchermo: 1,77" 160 x 128, 65 K coloriReti: GSM-EDGE 850/900/1800/1900 MHzTasto per le chiamate d'emergenzaTasti di grandi dimensioniLuce LEDFotocamera VGABluetoothMP3Radio FMMicro USBMicro SD (scheda non inclusa)Agenda: 250 vociFunzione SMS, MMSkit auricolare mani libereBatteria Li-ion 800 mAhDurata della batteria: 220 h in standby / 5,5 h in conversazioneInclude: batteria, base di ricarica, adattatore di CA, cavo dati USB e auricolariDimensioni (senza base): 5 x 11 x 1,4 cm circa.</br><a href=""#maggiorni_informazioni"" title=""Telefono Cellulare Thomson Serea51""> Maggiori Informazioni</a>",0.288,1,"Taxable Goods","Catalog, Search",85.9,,,,Telefono-Cellulare-Thomson-Serea51-Bianco,"Telefono Cellulare Thomson Serea51 Bianco","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Bianco,Bianco,","In arrivo il telefono cellulare Thomson Serea51 per gli amanti dei cellulari semplici e funzionali, per le persone anziane o per coloro che si avviciano per la prima volta al mondo della telefonia mobile!Caratteristiche:Cellulare sbloccatoSchermo: 1",http://dropshipping.bigbuy.eu/imgs/V1400103_91207.jpg,,http://dropshipping.bigbuy.eu/imgs/V1400103_91207.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3527570046964",42,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1400103_91208.jpg,http://dropshipping.bigbuy.eu/imgs/V1400103_91206.jpg","GTIN=3527570046964",,,,,,,,,, +BB-V1400105,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Telefono Cellulare Thomson Serea51 Nero","<a id=""maggiorni_informazioni"" title=""Telefono Cellulare Thomson Serea51""><p>In</a> arrivo il <strong>telefono cellulare</strong><strong> Thomson Serea51</strong><strong> </strong>per gli amanti dei cellulari semplici e funzionali, per le persone anziane o per coloro che si avviciano per la prima volta al mondo della telefonia mobile!</p><p>Caratteristiche:</p><ul><li>Cellulare sbloccato</li><li>Schermo: 1,77"" 160 x 128, 65 K colori</li><li>Reti: GSM-EDGE 850/900/1800/1900 MHz</li><li>Tasto per le chiamate d'emergenza</li><li>Tasti di grandi dimensioni</li><li>Luce LED</li><li>Fotocamera VGA</li><li>Bluetooth</li><li>MP3</li><li>Radio FM</li><li>Micro USB</li><li>Micro SD (scheda non inclusa)</li><li>Agenda: 250 voci</li><li>Funzione SMS, MMS</li><li>kit auricolare mani libere</li><li>Batteria Li-ion 800 mAh</li><li>Durata della batteria: 220 h in standby / 5,5 h in conversazione</li><li>Include: batteria, base di ricarica, adattatore di CA, cavo dati USB e auricolari</li><li>Dimensioni (senza base): 5 x 11 x 1,4 cm circa</li></ul><p> Dimenzioni per Telefono Cellulare Thomson Serea51: </br><ul><li>Altezza: 18 Cm</li><li>Larghezza: 11.5 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.288 Kg</li></ul></p><p>Codice Prodotto (EAN): 3527570046186</p>","In arrivo il telefono cellulare Thomson Serea51 per gli amanti dei cellulari semplici e funzionali, per le persone anziane o per coloro che si avviciano per la prima volta al mondo della telefonia mobile!Caratteristiche:Cellulare sbloccatoSchermo: 1,77" 160 x 128, 65 K coloriReti: GSM-EDGE 850/900/1800/1900 MHzTasto per le chiamate d'emergenzaTasti di grandi dimensioniLuce LEDFotocamera VGABluetoothMP3Radio FMMicro USBMicro SD (scheda non inclusa)Agenda: 250 vociFunzione SMS, MMSkit auricolare mani libereBatteria Li-ion 800 mAhDurata della batteria: 220 h in standby / 5,5 h in conversazioneInclude: batteria, base di ricarica, adattatore di CA, cavo dati USB e auricolariDimensioni (senza base): 5 x 11 x 1,4 cm circa.</br><a href=""#maggiorni_informazioni"" title=""Telefono Cellulare Thomson Serea51""> Maggiori Informazioni</a>",0.288,1,"Taxable Goods","Catalog, Search",85.9,,,,Telefono-Cellulare-Thomson-Serea51-Nero,"Telefono Cellulare Thomson Serea51 Nero","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Nero,Nero,","In arrivo il telefono cellulare Thomson Serea51 per gli amanti dei cellulari semplici e funzionali, per le persone anziane o per coloro che si avviciano per la prima volta al mondo della telefonia mobile!Caratteristiche:Cellulare sbloccatoSchermo: 1",http://dropshipping.bigbuy.eu/imgs/V1400103_91208.jpg,,http://dropshipping.bigbuy.eu/imgs/V1400103_91208.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3527570046186",33,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1400103_91207.jpg,http://dropshipping.bigbuy.eu/imgs/V1400103_91206.jpg","GTIN=3527570046186",,,,,,,,,, +BB-V1400107,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Telefono Cellularel Thomson Serea62 Nero","<a id=""maggiorni_informazioni"" title=""Telefono Cellularel Thomson Serea62""><p>Se</a> sei alla ricerca di un <strong>cellulare</strong> semplice ma dal design impeccabile, non puoi lasciarti sfuggire il <strong>telefono cellulare Thomson Serea62</strong>. È studiato anche per le persone anziane che vogliono avvicinarsi al mondo della <strong>telefonia mobile</strong>. </p><p>Caratteristiche:</p><ul><li>Schermo: 2,4"" 240 x 320, 262 K a colori</li><li>Reti: GSM-EDGE 850/900/1800/1900 MHz</li><li>Tasto per le chiamate d'emergenza</li><li>Menù semplificato e facile da usare</li><li>Tasti di grandi dimensioni</li><li>Fotocamera VGA</li><li>Bluetooth</li><li>MP3</li><li>Radio FM</li><li>Luce LED</li><li>Micro USB</li><li>Micro SD (carta non inclusa)</li><li>Agenda: 250 voci</li><li>Funzione SMS, MMS</li><li>kit auricolare mani libere</li><li>Batteria Li-ion 800 mAh</li><li>Durata della batteria: 480 h in standby / 5,5 h in conversazione</li><li>Include: batteria, base di ricarica, adattatore di CA, cavo dati USB e auricolari</li><li>Dimensioni (senza base): 5,5 x 10,5 x 2 cm circa</li></ul><p> Dimenzioni per Telefono Cellularel Thomson Serea62: </br><ul><li>Altezza: 5.2 Cm</li><li>Larghezza: 11.3 Cm</li><li>Profondita': 18.6 Cm</li><li>Peso: 0.284 Kg</li></ul></p><p>Codice Prodotto (EAN): 3527570046094</p>","Se sei alla ricerca di un cellulare semplice ma dal design impeccabile, non puoi lasciarti sfuggire il telefono cellulare Thomson Serea62.</br><a href=""#maggiorni_informazioni"" title=""Telefono Cellularel Thomson Serea62""> Maggiori Informazioni</a>",0.284,1,"Taxable Goods","Catalog, Search",99.5,,,,Telefono-Cellularel-Thomson-Serea62-Nero,"Telefono Cellularel Thomson Serea62 Nero","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Nero,Nero,","Se sei alla ricerca di un cellulare semplice ma dal design impeccabile, non puoi lasciarti sfuggire il telefono cellulare Thomson Serea62",http://dropshipping.bigbuy.eu/imgs/V1400106_91213.jpg,,http://dropshipping.bigbuy.eu/imgs/V1400106_91213.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3527570046094",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1400106_91215.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91214.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91212.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91211.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91210.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91209.jpg","GTIN=3527570046094",,,,,,,,,, +BB-V1400108,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Telefono Cellularel Thomson Serea62 Bianco","<a id=""maggiorni_informazioni"" title=""Telefono Cellularel Thomson Serea62""><p>Se</a> sei alla ricerca di un <strong>cellulare</strong> semplice ma dal design impeccabile, non puoi lasciarti sfuggire il <strong>telefono cellulare Thomson Serea62</strong>. È studiato anche per le persone anziane che vogliono avvicinarsi al mondo della <strong>telefonia mobile</strong>. </p><p>Caratteristiche:</p><ul><li>Schermo: 2,4"" 240 x 320, 262 K a colori</li><li>Reti: GSM-EDGE 850/900/1800/1900 MHz</li><li>Tasto per le chiamate d'emergenza</li><li>Menù semplificato e facile da usare</li><li>Tasti di grandi dimensioni</li><li>Fotocamera VGA</li><li>Bluetooth</li><li>MP3</li><li>Radio FM</li><li>Luce LED</li><li>Micro USB</li><li>Micro SD (carta non inclusa)</li><li>Agenda: 250 voci</li><li>Funzione SMS, MMS</li><li>kit auricolare mani libere</li><li>Batteria Li-ion 800 mAh</li><li>Durata della batteria: 480 h in standby / 5,5 h in conversazione</li><li>Include: batteria, base di ricarica, adattatore di CA, cavo dati USB e auricolari</li><li>Dimensioni (senza base): 5,5 x 10,5 x 2 cm circa</li></ul><p> Dimenzioni per Telefono Cellularel Thomson Serea62: </br><ul><li>Altezza: 5.2 Cm</li><li>Larghezza: 11.3 Cm</li><li>Profondita': 18.6 Cm</li><li>Peso: 0.284 Kg</li></ul></p><p>Codice Prodotto (EAN): 3527570046100</p>","Se sei alla ricerca di un cellulare semplice ma dal design impeccabile, non puoi lasciarti sfuggire il telefono cellulare Thomson Serea62.</br><a href=""#maggiorni_informazioni"" title=""Telefono Cellularel Thomson Serea62""> Maggiori Informazioni</a>",0.284,1,"Taxable Goods","Catalog, Search",99.5,,,,Telefono-Cellularel-Thomson-Serea62-Bianco,"Telefono Cellularel Thomson Serea62 Bianco","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Bianco,Bianco,","Se sei alla ricerca di un cellulare semplice ma dal design impeccabile, non puoi lasciarti sfuggire il telefono cellulare Thomson Serea62",http://dropshipping.bigbuy.eu/imgs/V1400106_91215.jpg,,http://dropshipping.bigbuy.eu/imgs/V1400106_91215.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3527570046100",13,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1400106_91213.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91214.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91212.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91211.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91210.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91209.jpg","GTIN=3527570046100",,,,,,,,,, +BB-V1400109,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Telefonia e Accessori,Default Category/Informatica Elettronica/Telefonia e Accessori/Cellulari",base,"Telefono Cellularel Thomson Serea62 Rosso","<a id=""maggiorni_informazioni"" title=""Telefono Cellularel Thomson Serea62""><p>Se</a> sei alla ricerca di un <strong>cellulare</strong> semplice ma dal design impeccabile, non puoi lasciarti sfuggire il <strong>telefono cellulare Thomson Serea62</strong>. È studiato anche per le persone anziane che vogliono avvicinarsi al mondo della <strong>telefonia mobile</strong>. </p><p>Caratteristiche:</p><ul><li>Schermo: 2,4"" 240 x 320, 262 K a colori</li><li>Reti: GSM-EDGE 850/900/1800/1900 MHz</li><li>Tasto per le chiamate d'emergenza</li><li>Menù semplificato e facile da usare</li><li>Tasti di grandi dimensioni</li><li>Fotocamera VGA</li><li>Bluetooth</li><li>MP3</li><li>Radio FM</li><li>Luce LED</li><li>Micro USB</li><li>Micro SD (carta non inclusa)</li><li>Agenda: 250 voci</li><li>Funzione SMS, MMS</li><li>kit auricolare mani libere</li><li>Batteria Li-ion 800 mAh</li><li>Durata della batteria: 480 h in standby / 5,5 h in conversazione</li><li>Include: batteria, base di ricarica, adattatore di CA, cavo dati USB e auricolari</li><li>Dimensioni (senza base): 5,5 x 10,5 x 2 cm circa</li></ul><p> Dimenzioni per Telefono Cellularel Thomson Serea62: </br><ul><li>Altezza: 5.2 Cm</li><li>Larghezza: 11.3 Cm</li><li>Profondita': 18.6 Cm</li><li>Peso: 0.284 Kg</li></ul></p><p>Codice Prodotto (EAN): 3527570046117</p>","Se sei alla ricerca di un cellulare semplice ma dal design impeccabile, non puoi lasciarti sfuggire il telefono cellulare Thomson Serea62.</br><a href=""#maggiorni_informazioni"" title=""Telefono Cellularel Thomson Serea62""> Maggiori Informazioni</a>",0.284,1,"Taxable Goods","Catalog, Search",99.5,,,,Telefono-Cellularel-Thomson-Serea62-Rosso,"Telefono Cellularel Thomson Serea62 Rosso","Informatica Elettronica,Informatica,Elettronica,Telefonia e Accessori,Telefonia,Accessori,Cellulari,Colore Rosso,Rosso,","Se sei alla ricerca di un cellulare semplice ma dal design impeccabile, non puoi lasciarti sfuggire il telefono cellulare Thomson Serea62",http://dropshipping.bigbuy.eu/imgs/V1400106_91214.jpg,,http://dropshipping.bigbuy.eu/imgs/V1400106_91214.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=3527570046117",17,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1400106_91213.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91215.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91212.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91211.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91210.jpg,http://dropshipping.bigbuy.eu/imgs/V1400106_91209.jpg","GTIN=3527570046117",,,,,,,,,, +BB-V0100186,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Cuffie",base,"Cuffie Fatina maginca Playz Kidz","<a id=""maggiorni_informazioni"" title=""Cuffie Fatina maginca Playz Kidz""><p>Le</a> <strong>cuffie Fatina Magica Playz Kidz </strong>son perfetti per i piccoli di casa! Queste <strong><strong>cuffie</strong> per bambini</strong> sono ideali come regalo per i re della casa!</p><p><a href=""http://www.playzkidz.com"" target=""_blank""><strong>www.playzkidz.com</strong></a></p><ul><li>Auricolari stereo</li><li>Cuffie imbottite</li><li>Compatibili con MP3, MP4, CD, radio e PC</li><li>Età raccomandata: +4 anni</li></ul><p> Dimenzioni per Cuffie Fatina maginca Playz Kidz: </br><ul><li>Altezza: 22.2 Cm</li><li>Larghezza: 9.5 Cm</li><li>Profondita': 26.7 Cm</li><li>Peso: 0.243 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888111122</p>","Le cuffie Fatina Magica Playz Kidz son perfetti per i piccoli di casa! Queste cuffie per bambini sono ideali come regalo per i re della casa!www.</br><a href=""#maggiorni_informazioni"" title=""Cuffie Fatina maginca Playz Kidz""> Maggiori Informazioni</a>",0.243,1,"Taxable Goods","Catalog, Search",18.9,,,,Cuffie-Fatina-maginca-Playz-Kidz,"Cuffie Fatina maginca Playz Kidz","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Cuffie,","Le cuffie Fatina Magica Playz Kidz son perfetti per i piccoli di casa! Queste cuffie per bambini sono ideali come regalo per i re della casa!www",http://dropshipping.bigbuy.eu/imgs/V0100186_93443.jpg,,http://dropshipping.bigbuy.eu/imgs/V0100186_93443.jpg,,,,,,"2016-08-16 05:37:23",,,,,,,,,,,,,,,,,"GTIN=4899888111122",2436,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0100186_93379.jpg,http://dropshipping.bigbuy.eu/imgs/V0100186_93377.jpg,http://dropshipping.bigbuy.eu/imgs/V0100186_93376.jpg,http://dropshipping.bigbuy.eu/imgs/V0100186_93375.jpg","GTIN=4899888111122",,,,,,,,,, +BB-V0100187,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Audio e Hi-Fi,Default Category/Informatica Elettronica/Audio e Hi-Fi/Cuffie",base,"Cuffie Mostriciattoli Playz Kidz","<a id=""maggiorni_informazioni"" title=""Cuffie Mostriciattoli Playz Kidz""><p>I</a> piccoli di casa impazziranno per le <strong><strong>cuffie</strong> Mostriciattoli Playz Kidz</strong>! Grazie al loro design originale e divertente, queste <strong><strong>cuffie</strong> per bambini </strong>sono il regalo perfetto!</p><p><a href=""http://www.playzkidz.com"" target=""_blank""><strong>www.playzkidz.com</strong></a></p><ul><li>Auricolari stereo</li><li>Cuffie imbottite</li><li>Compatibili con MP3, MP4, CD, radio e PC</li><li>Età raccomandata: +4 anni</li></ul><p> Dimenzioni per Cuffie Mostriciattoli Playz Kidz: </br><ul><li>Altezza: 22.2 Cm</li><li>Larghezza: 9.5 Cm</li><li>Profondita': 26.7 Cm</li><li>Peso: 0.245 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888111139</p>","I piccoli di casa impazziranno per le cuffie Mostriciattoli Playz Kidz! Grazie al loro design originale e divertente, queste cuffie per bambini sono il regalo perfetto!www.</br><a href=""#maggiorni_informazioni"" title=""Cuffie Mostriciattoli Playz Kidz""> Maggiori Informazioni</a>",0.245,1,"Taxable Goods","Catalog, Search",18.9,,,,Cuffie-Mostriciattoli-Playz-Kidz,"Cuffie Mostriciattoli Playz Kidz","Informatica Elettronica,Informatica,Elettronica,Audio e Hi-Fi,Audio,Hi-Fi,Cuffie,","I piccoli di casa impazziranno per le cuffie Mostriciattoli Playz Kidz! Grazie al loro design originale e divertente, queste cuffie per bambini sono il regalo perfetto!www",http://dropshipping.bigbuy.eu/imgs/V0100187_93370.jpg,,http://dropshipping.bigbuy.eu/imgs/V0100187_93370.jpg,,,,,,"2016-08-30 08:26:19",,,,,,,,,,,,,,,,,"GTIN=4899888111139",2438,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0100187_93374.jpg,http://dropshipping.bigbuy.eu/imgs/V0100187_93373.jpg,http://dropshipping.bigbuy.eu/imgs/V0100187_93372.jpg,http://dropshipping.bigbuy.eu/imgs/V0100187_93371.jpg","GTIN=4899888111139",,,,,,,,,, +BB-V1300174,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Orologi e Sveglie,Default Category/Informatica Elettronica/Orologi e Sveglie/Sveglie",base,"Orologio Sveglia con Contasecondi Star Wars R2-D2","<a id=""maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""><p>Sorprendi</a> i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'<strong>o</strong><strong>rologio sveglia con contasecondi Star Wars</strong>! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</p><ul><li>Dispone di segnale acustico d'allarme e di pulsante per spegnerlo</li><li>Funziona a batterie (1 x AA, non inclusa)</li><li>Dimensioni: circa 10,5 x 13,5 x 6 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Orologio Sveglia con Contasecondi Star Wars: </br><ul><li>Altezza: 0.13 Cm</li><li>Larghezza: 0.1 Cm</li><li>Profondita': 0.5 Cm</li><li>Peso: 0.15 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934787128</p>","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</br><a href=""#maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""> Maggiori Informazioni</a>",0.15,1,"Taxable Goods","Catalog, Search",15.8,,,,Orologio-Sveglia-con-Contasecondi-Star-Wars-R2-D2,"Orologio Sveglia con Contasecondi Star Wars R2-D2","Informatica Elettronica,Informatica,Elettronica,Orologi e Sveglie,Orologi,Sveglie,Sveglie,Design R2-D2,R2-D2,","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi",http://dropshipping.bigbuy.eu/imgs/V1300173_102640.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300173_102640.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934787128",69,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300173_102644.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102643.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102642.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102641.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102639.jpg","GTIN=8427934787128",,,,,,,,,, +BB-V1300175,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Orologi e Sveglie,Default Category/Informatica Elettronica/Orologi e Sveglie/Sveglie",base,"Orologio Sveglia con Contasecondi Star Wars Stormtrooper","<a id=""maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""><p>Sorprendi</a> i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'<strong>o</strong><strong>rologio sveglia con contasecondi Star Wars</strong>! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</p><ul><li>Dispone di segnale acustico d'allarme e di pulsante per spegnerlo</li><li>Funziona a batterie (1 x AA, non inclusa)</li><li>Dimensioni: circa 10,5 x 13,5 x 6 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Orologio Sveglia con Contasecondi Star Wars: </br><ul><li>Altezza: 0.13 Cm</li><li>Larghezza: 0.1 Cm</li><li>Profondita': 0.5 Cm</li><li>Peso: 0.15 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934787135</p>","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</br><a href=""#maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""> Maggiori Informazioni</a>",0.15,1,"Taxable Goods","Catalog, Search",15.8,,,,Orologio-Sveglia-con-Contasecondi-Star-Wars-Stormtrooper,"Orologio Sveglia con Contasecondi Star Wars Stormtrooper","Informatica Elettronica,Informatica,Elettronica,Orologi e Sveglie,Orologi,Sveglie,Sveglie,Design Stormtrooper,Stormtrooper,","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi",http://dropshipping.bigbuy.eu/imgs/V1300173_102644.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300173_102644.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934787135",69,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300173_102640.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102643.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102642.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102641.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102639.jpg","GTIN=8427934787135",,,,,,,,,, +BB-V1300176,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Orologi e Sveglie,Default Category/Informatica Elettronica/Orologi e Sveglie/Sveglie",base,"Orologio Sveglia con Contasecondi Star Wars Yoda","<a id=""maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""><p>Sorprendi</a> i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'<strong>o</strong><strong>rologio sveglia con contasecondi Star Wars</strong>! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</p><ul><li>Dispone di segnale acustico d'allarme e di pulsante per spegnerlo</li><li>Funziona a batterie (1 x AA, non inclusa)</li><li>Dimensioni: circa 10,5 x 13,5 x 6 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Orologio Sveglia con Contasecondi Star Wars: </br><ul><li>Altezza: 0.13 Cm</li><li>Larghezza: 0.1 Cm</li><li>Profondita': 0.5 Cm</li><li>Peso: 0.15 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934787142</p>","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</br><a href=""#maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""> Maggiori Informazioni</a>",0.15,1,"Taxable Goods","Catalog, Search",15.8,,,,Orologio-Sveglia-con-Contasecondi-Star-Wars-Yoda,"Orologio Sveglia con Contasecondi Star Wars Yoda","Informatica Elettronica,Informatica,Elettronica,Orologi e Sveglie,Orologi,Sveglie,Sveglie,Design Yoda,Yoda,","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi",http://dropshipping.bigbuy.eu/imgs/V1300173_102643.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300173_102643.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934787142",68,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300173_102640.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102644.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102642.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102641.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102639.jpg","GTIN=8427934787142",,,,,,,,,, +BB-V1300177,,Default,simple,"Default Category/Informatica Elettronica,Default Category/Informatica Elettronica/Orologi e Sveglie,Default Category/Informatica Elettronica/Orologi e Sveglie/Sveglie",base,"Orologio Sveglia con Contasecondi Star Wars Chewbacca","<a id=""maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""><p>Sorprendi</a> i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'<strong>o</strong><strong>rologio sveglia con contasecondi Star Wars</strong>! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</p><ul><li>Dispone di segnale acustico d'allarme e di pulsante per spegnerlo</li><li>Funziona a batterie (1 x AA, non inclusa)</li><li>Dimensioni: circa 10,5 x 13,5 x 6 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Orologio Sveglia con Contasecondi Star Wars: </br><ul><li>Altezza: 0.13 Cm</li><li>Larghezza: 0.1 Cm</li><li>Profondita': 0.5 Cm</li><li>Peso: 0.15 Kg</li></ul></p><p>Codice Prodotto (EAN): 8427934787159</p>","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi.</br><a href=""#maggiorni_informazioni"" title=""Orologio Sveglia con Contasecondi Star Wars""> Maggiori Informazioni</a>",0.15,1,"Taxable Goods","Catalog, Search",15.8,,,,Orologio-Sveglia-con-Contasecondi-Star-Wars-Chewbacca,"Orologio Sveglia con Contasecondi Star Wars Chewbacca","Informatica Elettronica,Informatica,Elettronica,Orologi e Sveglie,Orologi,Sveglie,Sveglie,Design Chewbacca,Chewbacca,","Sorprendi i tuoi familiari ed amici con un regalo originale che li lascerà a bocca aperta, l'orologio sveglia con contasecondi Star Wars! Una sveglia ideale per gli appassionati della famosa saga e dei suoi personaggi",http://dropshipping.bigbuy.eu/imgs/V1300173_102642.jpg,,http://dropshipping.bigbuy.eu/imgs/V1300173_102642.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8427934787159",69,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1300173_102640.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102644.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102643.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102641.jpg,http://dropshipping.bigbuy.eu/imgs/V1300173_102639.jpg","GTIN=8427934787159",,,,,,,,,, +BB-G1000110,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Offerte",base,"Attrezzo da Ginnastica Body Rocker","<a id=""maggiorni_informazioni"" title=""Attrezzo da Ginnastica Body Rocker ""><p>Non</a> perdere tempo e denaro andandoin <strong>palestra</strong> e procurarti ora l'<strong>attrezzo da ginnastica Body Rocker! </strong>Una forma facile ed efficace di rimettresi in forma facendo esercizio in casa. Grazie al suo sistema di oscillazione, è perfetto per lavorare e rafforzare, spalle, braccia, schiena, petto, glutei, ecc. Fatto in acciaio con impugnature in gomma. Include manuale d'istruzioni e DVD dimostrativo. (Questo prodotto può può presentare lievi danni che non impediscono il funzionamento del prodotto: impugnature in gomma piuma scollate).</p><p> Dimenzioni per Attrezzo da Ginnastica Body Rocker : </br><ul><li>Altezza: 21 Cm</li><li>Larghezza: 20 Cm</li><li>Profondita': 78 Cm</li><li>Peso: 2.13 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888101314</p>","Non perdere tempo e denaro andandoin palestra e procurarti ora l'attrezzo da ginnastica Body Rocker! Una forma facile ed efficace di rimettresi in forma facendo esercizio in casa.</br><a href=""#maggiorni_informazioni"" title=""Attrezzo da Ginnastica Body Rocker ""> Maggiori Informazioni</a>",2.13,1,"Taxable Goods","Catalog, Search",79,,,,Attrezzo-da-Ginnastica-Body-Rocker,"Attrezzo da Ginnastica Body Rocker","Outlet Offerte,Outlet,Offerte,Offerte,","Non perdere tempo e denaro andandoin palestra e procurarti ora l'attrezzo da ginnastica Body Rocker! Una forma facile ed efficace di rimettresi in forma facendo esercizio in casa",http://dropshipping.bigbuy.eu/imgs/bodirocker-00.jpg,,http://dropshipping.bigbuy.eu/imgs/bodirocker-00.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=4899888101314",121,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/bodirocker-03.jpg,http://dropshipping.bigbuy.eu/imgs/bodirocker-06.jpg,http://dropshipping.bigbuy.eu/imgs/bodirocker-05.jpg,http://dropshipping.bigbuy.eu/imgs/bodirocker-07.jpg,http://dropshipping.bigbuy.eu/imgs/bodirocker-02.jpg,http://dropshipping.bigbuy.eu/imgs/bodirocker-01.jpg,http://dropshipping.bigbuy.eu/imgs/bodirocker-04.jpg","GTIN=4899888101314",,,,,,,,,, +BB-J2000066,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Lovely Arancio","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""><p><strong>Acquista</a> la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali</strong>.<strong> </strong>La <strong>coperta Snug Snug con le maniche</strong> ti riscalderà senza aver bisogno del riscaldamento per tutto il giorno. Risparmierai i soldi e starai comodo grazie alla <strong>coperta extra morbida Kangoo Snug Snug con le maniche</strong>, poiché potrai fare tutto ciò che vuoi mentre sei coperto.</p><p>La coperta Snug Snug con le maniche è il regalo perfetto per qualsiasi occasione. Nessuno si aspetta un regalo così straordinario! Questa coperta con le maniche è l'ideale per quei momenti in cui fa freddo fuori e vuoi solo stare a casa a leggere, usare il computer o semplicemente a rilassarti sul divano. Fino ad ora, non è era facile raggomitolarsi sul divano e voler fare qualcosa, perché dovevi tirare fuori le braccia, ma faceva freddo. Tutto questo è finito grazie alla coperta Kangoo Snug Snug con le maniche!</p><p>Caratteristiche:</p><ul><li>Fatto di pile ultra soffice</li><li>Lavabile in lavatrice</li><li>Dimensioni: lunghezza 185 cm, larghezza 160 cm | Dimensioni manica: 60cm</li></ul><p><a title=""Batamanta SnugSnug"" href=""http://www.snugsnug.com"" target=""_blank"">www.snugsnug.com</a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio): </br><ul><li>Altezza: 28 Cm</li><li>Larghezza: 26 Cm</li><li>Profondita': 12 Cm</li><li>Peso: 0.8 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102731</p>","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""> Maggiori Informazioni</a>",0.8,1,"Taxable Goods","Catalog, Search",22.9,,,,OUTLET-Coperta-super-soffice-Kangoo-Snug-Snug-con-maniche-per-adulti-|-Decorazioni-originali-(Senza-imballaggio)-Lovely Arancio,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Lovely Arancio","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Lovely Arancio,Lovely Arancio,","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali",http://dropshipping.bigbuy.eu/imgs/J2000065_91715.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000065_91715.jpg,,,,,,"2016-02-11 15:49:04",,,,,,,,,,,,,,,,,"GTIN=4899888102731",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000065_91719.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91718.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91717.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91716.jpg","GTIN=4899888102731",,,,,,,,,, +BB-J2000239,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Commando","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""><p><strong>Acquista</a> la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali</strong>.<strong> </strong>La <strong>coperta Snug Snug con le maniche</strong> ti riscalderà senza aver bisogno del riscaldamento per tutto il giorno. Risparmierai i soldi e starai comodo grazie alla <strong>coperta extra morbida Kangoo Snug Snug con le maniche</strong>, poiché potrai fare tutto ciò che vuoi mentre sei coperto.</p><p>La coperta Snug Snug con le maniche è il regalo perfetto per qualsiasi occasione. Nessuno si aspetta un regalo così straordinario! Questa coperta con le maniche è l'ideale per quei momenti in cui fa freddo fuori e vuoi solo stare a casa a leggere, usare il computer o semplicemente a rilassarti sul divano. Fino ad ora, non è era facile raggomitolarsi sul divano e voler fare qualcosa, perché dovevi tirare fuori le braccia, ma faceva freddo. Tutto questo è finito grazie alla coperta Kangoo Snug Snug con le maniche!</p><p>Caratteristiche:</p><ul><li>Fatto di pile ultra soffice</li><li>Lavabile in lavatrice</li><li>Dimensioni: lunghezza 185 cm, larghezza 160 cm | Dimensioni manica: 60cm</li></ul><p><a title=""Batamanta SnugSnug"" href=""http://www.snugsnug.com"" target=""_blank"">www.snugsnug.com</a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio): </br><ul><li>Altezza: 28 Cm</li><li>Larghezza: 26 Cm</li><li>Profondita': 12 Cm</li><li>Peso: 0.8 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102731</p>","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""> Maggiori Informazioni</a>",0.8,1,"Taxable Goods","Catalog, Search",22.9,,,,OUTLET-Coperta-super-soffice-Kangoo-Snug-Snug-con-maniche-per-adulti-|-Decorazioni-originali-(Senza-imballaggio)-Commando,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Commando","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Commando,Commando,","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali",http://dropshipping.bigbuy.eu/imgs/J2000065_91719.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000065_91719.jpg,,,,,,"2016-02-11 15:49:04",,,,,,,,,,,,,,,,,"GTIN=4899888102731",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000065_91715.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91718.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91717.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91716.jpg","GTIN=4899888102731",,,,,,,,,, +BB-J2000240,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Galaktic","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""><p><strong>Acquista</a> la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali</strong>.<strong> </strong>La <strong>coperta Snug Snug con le maniche</strong> ti riscalderà senza aver bisogno del riscaldamento per tutto il giorno. Risparmierai i soldi e starai comodo grazie alla <strong>coperta extra morbida Kangoo Snug Snug con le maniche</strong>, poiché potrai fare tutto ciò che vuoi mentre sei coperto.</p><p>La coperta Snug Snug con le maniche è il regalo perfetto per qualsiasi occasione. Nessuno si aspetta un regalo così straordinario! Questa coperta con le maniche è l'ideale per quei momenti in cui fa freddo fuori e vuoi solo stare a casa a leggere, usare il computer o semplicemente a rilassarti sul divano. Fino ad ora, non è era facile raggomitolarsi sul divano e voler fare qualcosa, perché dovevi tirare fuori le braccia, ma faceva freddo. Tutto questo è finito grazie alla coperta Kangoo Snug Snug con le maniche!</p><p>Caratteristiche:</p><ul><li>Fatto di pile ultra soffice</li><li>Lavabile in lavatrice</li><li>Dimensioni: lunghezza 185 cm, larghezza 160 cm | Dimensioni manica: 60cm</li></ul><p><a title=""Batamanta SnugSnug"" href=""http://www.snugsnug.com"" target=""_blank"">www.snugsnug.com</a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio): </br><ul><li>Altezza: 28 Cm</li><li>Larghezza: 26 Cm</li><li>Profondita': 12 Cm</li><li>Peso: 0.8 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102731</p>","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""> Maggiori Informazioni</a>",0.8,1,"Taxable Goods","Catalog, Search",22.9,,,,OUTLET-Coperta-super-soffice-Kangoo-Snug-Snug-con-maniche-per-adulti-|-Decorazioni-originali-(Senza-imballaggio)-Galaktic,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Galaktic","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Galaktic,Galaktic,","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali",http://dropshipping.bigbuy.eu/imgs/J2000065_91718.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000065_91718.jpg,,,,,,"2016-02-11 15:49:04",,,,,,,,,,,,,,,,,"GTIN=4899888102731",14,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000065_91715.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91719.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91717.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91716.jpg","GTIN=4899888102731",,,,,,,,,, +BB-J2000329,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Lovely Blu","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""><p><strong>Acquista</a> la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali</strong>.<strong> </strong>La <strong>coperta Snug Snug con le maniche</strong> ti riscalderà senza aver bisogno del riscaldamento per tutto il giorno. Risparmierai i soldi e starai comodo grazie alla <strong>coperta extra morbida Kangoo Snug Snug con le maniche</strong>, poiché potrai fare tutto ciò che vuoi mentre sei coperto.</p><p>La coperta Snug Snug con le maniche è il regalo perfetto per qualsiasi occasione. Nessuno si aspetta un regalo così straordinario! Questa coperta con le maniche è l'ideale per quei momenti in cui fa freddo fuori e vuoi solo stare a casa a leggere, usare il computer o semplicemente a rilassarti sul divano. Fino ad ora, non è era facile raggomitolarsi sul divano e voler fare qualcosa, perché dovevi tirare fuori le braccia, ma faceva freddo. Tutto questo è finito grazie alla coperta Kangoo Snug Snug con le maniche!</p><p>Caratteristiche:</p><ul><li>Fatto di pile ultra soffice</li><li>Lavabile in lavatrice</li><li>Dimensioni: lunghezza 185 cm, larghezza 160 cm | Dimensioni manica: 60cm</li></ul><p><a title=""Batamanta SnugSnug"" href=""http://www.snugsnug.com"" target=""_blank"">www.snugsnug.com</a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio): </br><ul><li>Altezza: 28 Cm</li><li>Larghezza: 26 Cm</li><li>Profondita': 12 Cm</li><li>Peso: 0.8 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102731</p>","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio)""> Maggiori Informazioni</a>",0.8,1,"Taxable Goods","Catalog, Search",22.9,,,,OUTLET-Coperta-super-soffice-Kangoo-Snug-Snug-con-maniche-per-adulti-|-Decorazioni-originali-(Senza-imballaggio)-Lovely Blu,"OUTLET Coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali (Senza imballaggio) Lovely Blu","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Lovely Blu,Lovely Blu,","Acquista la coperta super soffice Kangoo Snug Snug con maniche per adulti | Decorazioni originali",http://dropshipping.bigbuy.eu/imgs/J2000065_91717.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000065_91717.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=4899888102731",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000065_91715.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91719.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91718.jpg,http://dropshipping.bigbuy.eu/imgs/J2000065_91716.jpg","GTIN=4899888102731",,,,,,,,,, +BB-J2000085,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio) Rosa S","<a id=""maggiorni_informazioni"" title=""OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio)""><p>Acquista</a> <strong>Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo.</strong> Scopri il modo più facile per mantenerti caldo a casa quando c'è freddo! Devi solo riscaldare questi <strong>stivali rilassanti nel microonde</strong> per godere immediatamente del calore e del rilassamento ai tuoi piedi. Sono fatti di morbido tessuto polare e di un rivestimento in semi di lavanda che emana un <strong>profumo straordinario</strong> quando riscaldato. Goditi semplicemente il profumo rilassante! I Warm Hug Feet Stivali Riscaldabili al Microonde mantengono il calore per lungo tempo grazie ai semi di lavanda contenuti all'interno. Per pulire questi stivali riscaldabili, passaci sopra un panno umido. Non riscaldarmi mai per più di 2 minuti e non usarli se sono troppo caldi. Disponibili in rosa e blu.</p><p>Taglie (equivalenze approssimative)</p><ul><li>S (35 a 38)</li><li>M (38 a 41)</li><li>L (41 a 43)</li></ul><p>Rispettare sempre i limiti nel microonde</p><ul><li>800W</li><li>80ºC</li><li>990 sec</li></ul><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio): </br><ul><li>Altezza: 27 Cm</li><li>Larghezza: 10.5 Cm</li><li>Profondita': 29 Cm</li><li>Peso: 0.67 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899881028786</p>","Acquista Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio)""> Maggiori Informazioni</a>",0.67,1,"Taxable Goods","Catalog, Search",33.5,,,,OUTLET-Warm-Hug-Feet-Stivali-Riscaldabili-al-Microonde-(Senza-imballaggio)-Rosa-S,"OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio) Rosa S","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Rosa,Rosa,Taglia S,S,","Acquista Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-00.jpg,,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-00.jpg,,,,,,"2016-02-02 10:45:18",,,,,,,,,,,,,,,,,"GTIN=4899881028786",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-03.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-01.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-02.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-04.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-05.jpg","GTIN=4899881028786",,,,,,,,,, +BB-J2000086,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio) Viola M","<a id=""maggiorni_informazioni"" title=""OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio)""><p>Acquista</a> <strong>Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo.</strong> Scopri il modo più facile per mantenerti caldo a casa quando c'è freddo! Devi solo riscaldare questi <strong>stivali rilassanti nel microonde</strong> per godere immediatamente del calore e del rilassamento ai tuoi piedi. Sono fatti di morbido tessuto polare e di un rivestimento in semi di lavanda che emana un <strong>profumo straordinario</strong> quando riscaldato. Goditi semplicemente il profumo rilassante! I Warm Hug Feet Stivali Riscaldabili al Microonde mantengono il calore per lungo tempo grazie ai semi di lavanda contenuti all'interno. Per pulire questi stivali riscaldabili, passaci sopra un panno umido. Non riscaldarmi mai per più di 2 minuti e non usarli se sono troppo caldi. Disponibili in rosa e blu.</p><p>Taglie (equivalenze approssimative)</p><ul><li>S (35 a 38)</li><li>M (38 a 41)</li><li>L (41 a 43)</li></ul><p>Rispettare sempre i limiti nel microonde</p><ul><li>800W</li><li>80ºC</li><li>990 sec</li></ul><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio): </br><ul><li>Altezza: 27 Cm</li><li>Larghezza: 10.5 Cm</li><li>Profondita': 29 Cm</li><li>Peso: 0.67 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899881028786</p>","Acquista Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio)""> Maggiori Informazioni</a>",0.67,1,"Taxable Goods","Catalog, Search",33.5,,,,OUTLET-Warm-Hug-Feet-Stivali-Riscaldabili-al-Microonde-(Senza-imballaggio)-Viola-M,"OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio) Viola M","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Viola,Viola,Taglia M,M,","Acquista Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-03.jpg,,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-03.jpg,,,,,,"2016-02-02 10:45:18",,,,,,,,,,,,,,,,,"GTIN=4899881028786",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-00.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-01.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-02.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-04.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-05.jpg","GTIN=4899881028786",,,,,,,,,, +BB-J2000188,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio) Viola L","<a id=""maggiorni_informazioni"" title=""OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio)""><p>Acquista</a> <strong>Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo.</strong> Scopri il modo più facile per mantenerti caldo a casa quando c'è freddo! Devi solo riscaldare questi <strong>stivali rilassanti nel microonde</strong> per godere immediatamente del calore e del rilassamento ai tuoi piedi. Sono fatti di morbido tessuto polare e di un rivestimento in semi di lavanda che emana un <strong>profumo straordinario</strong> quando riscaldato. Goditi semplicemente il profumo rilassante! I Warm Hug Feet Stivali Riscaldabili al Microonde mantengono il calore per lungo tempo grazie ai semi di lavanda contenuti all'interno. Per pulire questi stivali riscaldabili, passaci sopra un panno umido. Non riscaldarmi mai per più di 2 minuti e non usarli se sono troppo caldi. Disponibili in rosa e blu.</p><p>Taglie (equivalenze approssimative)</p><ul><li>S (35 a 38)</li><li>M (38 a 41)</li><li>L (41 a 43)</li></ul><p>Rispettare sempre i limiti nel microonde</p><ul><li>800W</li><li>80ºC</li><li>990 sec</li></ul><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio): </br><ul><li>Altezza: 27 Cm</li><li>Larghezza: 10.5 Cm</li><li>Profondita': 29 Cm</li><li>Peso: 0.67 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899881028786</p>","Acquista Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio)""> Maggiori Informazioni</a>",0.67,1,"Taxable Goods","Catalog, Search",33.5,,,,OUTLET-Warm-Hug-Feet-Stivali-Riscaldabili-al-Microonde-(Senza-imballaggio)-Viola-L,"OUTLET Warm Hug Feet Stivali Riscaldabili al Microonde (Senza imballaggio) Viola L","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Viola,Viola,Taglia L,L,","Acquista Warm Hug Feet Stivali Riscaldabili al Microonde al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-01.jpg,,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-01.jpg,,,,,,"2016-02-02 10:45:18",,,,,,,,,,,,,,,,,"GTIN=4899881028786",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-00.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-03.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-02.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-04.jpg,http://dropshipping.bigbuy.eu/imgs/warm-hugh-feet-boots-05.jpg","GTIN=4899881028786",,,,,,,,,, +BB-J2000123,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Macchina per Gelato Princess 282602 (Senza imballaggio)","<a id=""maggiorni_informazioni"" title=""OUTLET Macchina per Gelato Princess 282602 (Senza imballaggio)""><p>Avviso</a> per gli amanti del gelato: ecco la nuovissima <strong>macchina per gelato</strong> <strong>Princess 282602</strong>. Una <strong>gelatiera</strong> diversa dalle altre, che ti permetterà di preparare in un batter d'occhio gelati dolci o salati per i grandi e i più piccini!<strong> </strong>Al naturale o con salsa di frutta, pepite o qualsiasi altra guarnizione...le tue papille ti ringrazieranno! Indispensabile per un'estate al fresco!</p><p>Basta mettere in freezer, per 12 ore circa, il recipiente rimovibile a forma di secchiello con manico integrato di 17,5 x 14,5 cm (diametro x altezza) ed è fatto! Il gelato 100% naturale è servito!</p><p>Caratteristiche:</p><ul><li>Potenza: 5 W</li><li>Frequenza: 50 Hz</li><li>Tensione: 220-240 V</li><li>Pulsante On/Off </li><li>Gommini antiscivolo</li><li>Scomparto per cavo di alimentazione</li><li>Mescolatore rimovibile</li><li>Facile da pulire</li><li>Apertura di riempimento (dimensioni approssimative: 5 x 5,5 cm)</li><li>Dimensioni approssimative: 22 x 25 x 22 cm</li></ul><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Macchina per Gelato Princess 282602 (Senza imballaggio): </br><ul><li>Altezza: 31 Cm</li><li>Larghezza: 23.4 Cm</li><li>Profondita': 23.5 Cm</li><li>Peso: 3.26 Kg</li></ul></p><p>Codice Prodotto (EAN): 8712836304895</p>","Avviso per gli amanti del gelato: ecco la nuovissima macchina per gelato Princess 282602.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Macchina per Gelato Princess 282602 (Senza imballaggio)""> Maggiori Informazioni</a>",3.26,1,"Taxable Goods","Catalog, Search",110.88,,,,OUTLET-Macchina-per-Gelato-Princess-282602-(Senza-imballaggio),"OUTLET Macchina per Gelato Princess 282602 (Senza imballaggio)","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,","Avviso per gli amanti del gelato: ecco la nuovissima macchina per gelato Princess 282602",http://dropshipping.bigbuy.eu/imgs/J2000123_88610.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000123_88610.jpg,,,,,,"2016-08-09 01:54:36",,,,,,,,,,,,,,,,,"GTIN=8712836304895",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000123_88612.jpg,http://dropshipping.bigbuy.eu/imgs/J2000123_88611.jpg","GTIN=8712836304895",,,,,,,,,, +BB-J2000176,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Celeste","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""><p><strong>Acquistare</a> Coperta Eden Deluxe </strong>160 x 240 al miglior prezzo<strong>. </strong>Questa fantastica <strong>coperta Eden Deluxe </strong>è perfetta per il tuo letto. La <strong>coperta Eden Dexuxe </strong>misura 160 x 240 cm. Questa coperta è veramente soffice e confortevole. La coperta Eden Deluxe è l'ideale per godere di un caldo inverno. 100% poliestere. Con comoda maniglia da trasporto.</p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio): </br><ul><li>Altezza: 43 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 51.5 Cm</li><li>Peso: 2.183 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436045510426</p>","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""> Maggiori Informazioni</a>",2.183,1,"Taxable Goods","Catalog, Search",57.9,,,,OUTLET-Coperta-Eden-Deluxe-160-x-240-(Senza-imballaggio)-Celeste,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Celeste","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Design Celeste,Celeste,","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,,,,,,"2016-02-17 15:45:14",,,,,,,,,,,,,,,,,"GTIN=8436045510426",5,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg","GTIN=8436045510426",,,,,,,,,, +BB-J2000178,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Rosso","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""><p><strong>Acquistare</a> Coperta Eden Deluxe </strong>160 x 240 al miglior prezzo<strong>. </strong>Questa fantastica <strong>coperta Eden Deluxe </strong>è perfetta per il tuo letto. La <strong>coperta Eden Dexuxe </strong>misura 160 x 240 cm. Questa coperta è veramente soffice e confortevole. La coperta Eden Deluxe è l'ideale per godere di un caldo inverno. 100% poliestere. Con comoda maniglia da trasporto.</p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio): </br><ul><li>Altezza: 43 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 51.5 Cm</li><li>Peso: 2.183 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436045510426</p>","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""> Maggiori Informazioni</a>",2.183,1,"Taxable Goods","Catalog, Search",57.9,,,,OUTLET-Coperta-Eden-Deluxe-160-x-240-(Senza-imballaggio)-Rosso,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Rosso","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Design Rosso,Rosso,","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,,,,,,"2016-02-17 15:45:14",,,,,,,,,,,,,,,,,"GTIN=8436045510426",3,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg","GTIN=8436045510426",,,,,,,,,, +BB-J2000179,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Cioccolato","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""><p><strong>Acquistare</a> Coperta Eden Deluxe </strong>160 x 240 al miglior prezzo<strong>. </strong>Questa fantastica <strong>coperta Eden Deluxe </strong>è perfetta per il tuo letto. La <strong>coperta Eden Dexuxe </strong>misura 160 x 240 cm. Questa coperta è veramente soffice e confortevole. La coperta Eden Deluxe è l'ideale per godere di un caldo inverno. 100% poliestere. Con comoda maniglia da trasporto.</p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio): </br><ul><li>Altezza: 43 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 51.5 Cm</li><li>Peso: 2.183 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436045510426</p>","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""> Maggiori Informazioni</a>",2.183,1,"Taxable Goods","Catalog, Search",57.9,,,,OUTLET-Coperta-Eden-Deluxe-160-x-240-(Senza-imballaggio)-Cioccolato,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Cioccolato","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Design Cioccolato,Cioccolato,","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,,,,,,"2016-02-17 15:45:14",,,,,,,,,,,,,,,,,"GTIN=8436045510426",5,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg","GTIN=8436045510426",,,,,,,,,, +BB-J2000180,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Crudo","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""><p><strong>Acquistare</a> Coperta Eden Deluxe </strong>160 x 240 al miglior prezzo<strong>. </strong>Questa fantastica <strong>coperta Eden Deluxe </strong>è perfetta per il tuo letto. La <strong>coperta Eden Dexuxe </strong>misura 160 x 240 cm. Questa coperta è veramente soffice e confortevole. La coperta Eden Deluxe è l'ideale per godere di un caldo inverno. 100% poliestere. Con comoda maniglia da trasporto.</p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio): </br><ul><li>Altezza: 43 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 51.5 Cm</li><li>Peso: 2.183 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436045510426</p>","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""> Maggiori Informazioni</a>",2.183,1,"Taxable Goods","Catalog, Search",57.9,,,,OUTLET-Coperta-Eden-Deluxe-160-x-240-(Senza-imballaggio)-Crudo,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Crudo","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Design Crudo,Crudo,","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,,,,,,"2016-02-17 15:45:14",,,,,,,,,,,,,,,,,"GTIN=8436045510426",9,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg","GTIN=8436045510426",,,,,,,,,, +BB-J2000181,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Fragola","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""><p><strong>Acquistare</a> Coperta Eden Deluxe </strong>160 x 240 al miglior prezzo<strong>. </strong>Questa fantastica <strong>coperta Eden Deluxe </strong>è perfetta per il tuo letto. La <strong>coperta Eden Dexuxe </strong>misura 160 x 240 cm. Questa coperta è veramente soffice e confortevole. La coperta Eden Deluxe è l'ideale per godere di un caldo inverno. 100% poliestere. Con comoda maniglia da trasporto.</p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio): </br><ul><li>Altezza: 43 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 51.5 Cm</li><li>Peso: 2.183 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436045510426</p>","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""> Maggiori Informazioni</a>",2.183,1,"Taxable Goods","Catalog, Search",57.9,,,,OUTLET-Coperta-Eden-Deluxe-160-x-240-(Senza-imballaggio)-Fragola,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Fragola","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Design Fragola,Fragola,","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,,,,,,"2016-02-17 15:45:14",,,,,,,,,,,,,,,,,"GTIN=8436045510426",8,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg","GTIN=8436045510426",,,,,,,,,, +BB-J2000182,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Cardinale","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""><p><strong>Acquistare</a> Coperta Eden Deluxe </strong>160 x 240 al miglior prezzo<strong>. </strong>Questa fantastica <strong>coperta Eden Deluxe </strong>è perfetta per il tuo letto. La <strong>coperta Eden Dexuxe </strong>misura 160 x 240 cm. Questa coperta è veramente soffice e confortevole. La coperta Eden Deluxe è l'ideale per godere di un caldo inverno. 100% poliestere. Con comoda maniglia da trasporto.</p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio): </br><ul><li>Altezza: 43 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 51.5 Cm</li><li>Peso: 2.183 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436045510426</p>","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""> Maggiori Informazioni</a>",2.183,1,"Taxable Goods","Catalog, Search",57.9,,,,OUTLET-Coperta-Eden-Deluxe-160-x-240-(Senza-imballaggio)-Cardinale,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Cardinale","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Design Cardinale,Cardinale,","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg,,,,,,"2016-02-17 15:45:14",,,,,,,,,,,,,,,,,"GTIN=8436045510426",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg","GTIN=8436045510426",,,,,,,,,, +BB-J2000247,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Turchese","<a id=""maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""><p><strong>Acquistare</a> Coperta Eden Deluxe </strong>160 x 240 al miglior prezzo<strong>. </strong>Questa fantastica <strong>coperta Eden Deluxe </strong>è perfetta per il tuo letto. La <strong>coperta Eden Dexuxe </strong>misura 160 x 240 cm. Questa coperta è veramente soffice e confortevole. La coperta Eden Deluxe è l'ideale per godere di un caldo inverno. 100% poliestere. Con comoda maniglia da trasporto.</p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio): </br><ul><li>Altezza: 43 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 51.5 Cm</li><li>Peso: 2.183 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436045510426</p>","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio)""> Maggiori Informazioni</a>",2.183,1,"Taxable Goods","Catalog, Search",57.9,,,,OUTLET-Coperta-Eden-Deluxe-160-x-240-(Senza-imballaggio)-Turchese,"OUTLET Coperta Eden Deluxe 160 x 240 (Senza imballaggio) Turchese","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Design Turchese,Turchese,","Acquistare Coperta Eden Deluxe 160 x 240 al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000175_88982.jpg,,,,,,"2016-02-17 15:45:14",,,,,,,,,,,,,,,,,"GTIN=8436045510426",7,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000175_88981.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88987.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88986.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88985.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88984.jpg,http://dropshipping.bigbuy.eu/imgs/J2000175_88983.jpg","GTIN=8436045510426",,,,,,,,,, +BB-J2000177,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Macchina RC Ferrari 599 GTO (Senza imballaggio)","<a id=""maggiorni_informazioni"" title=""OUTLET Macchina RC Ferrari 599 GTO (Senza imballaggio)""><p>Hai</a> visto la <strong>macchina RC Ferrari 599 GTO</strong>? Puoi facilmente usare questo divertente giocattolo<strong> autorizzato Ferrari</strong> grazie al suo telecomando. La macchina funziona a batterie 5 x AA (non incluse) e il telecomando a batteria 1 x 6F22 9V (non inclusa). Giocattolo adatto per bambini di età superiore ai 6 anni. Funzioni della macchina radiocontrollata Ferrari:</p><ul><li>Avanti e indietro</li><li>Gira a sinistra e a destra</li><li>Fari e Fanali posteriori</li></ul><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Macchina RC Ferrari 599 GTO (Senza imballaggio): </br><ul><li>Altezza: 17.5 Cm</li><li>Larghezza: 43.5 Cm</li><li>Profondita': 22.7 Cm</li><li>Peso: 1.244 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158011299</p>","Hai visto la macchina RC Ferrari 599 GTO? Puoi facilmente usare questo divertente giocattolo autorizzato Ferrari grazie al suo telecomando.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Macchina RC Ferrari 599 GTO (Senza imballaggio)""> Maggiori Informazioni</a>",1.244,1,"Taxable Goods","Catalog, Search",119,,,,OUTLET-Macchina-RC-Ferrari-599-GTO--(Senza-imballaggio),"OUTLET Macchina RC Ferrari 599 GTO (Senza imballaggio)","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,","Hai visto la macchina RC Ferrari 599 GTO? Puoi facilmente usare questo divertente giocattolo autorizzato Ferrari grazie al suo telecomando",http://dropshipping.bigbuy.eu/imgs/J2000177_89020.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000177_89020.jpg,,,,,,"2016-07-22 06:19:54",,,,,,,,,,,,,,,,,"GTIN=8718158011299",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000177_89022.jpg,http://dropshipping.bigbuy.eu/imgs/J2000177_89021.jpg","GTIN=8718158011299",,,,,,,,,, +BB-J2000255,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio) Beige S","<a id=""maggiorni_informazioni"" title=""OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio)""><p>Metti</a> in luce un corpo scultoreo con la <strong>Canottiera Modellante con Reggiseno Booby & Tummy! </strong>Discreta, comod. Sostiene il seno e offre una grande capacità di sostegno. Prodotta il un tessuto delicato, flessibile e traspirante che si adatta perfettamente al tuo corpo e offre una copertura completa (seno, fianchi e glutei).  Equivalenza di taglie: S: 36-38, M: 38-40, L: 40-42.</p><p><a href=""http://www.boobyandtummy.com/"" target=""_blank""><strong>www.boobyandtummy.com</strong></a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio): </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 13.5 Cm</li><li>Profondita': 7 Cm</li><li>Peso: 0.14 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888101352</p>","Metti in luce un corpo scultoreo con la Canottiera Modellante con Reggiseno Booby & Tummy! Discreta, comod.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio)""> Maggiori Informazioni</a>",0.14,1,"Taxable Goods","Catalog, Search",23.1,,,,OUTLET-Canottiera-Modellante-con-Reggiseno-Booby-&-Tummy--(Senza-imballaggio)-Beige-S,"OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio) Beige S","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Beige,Beige,Taglia S,S,","Metti in luce un corpo scultoreo con la Canottiera Modellante con Reggiseno Booby & Tummy! Discreta, comod",http://dropshipping.bigbuy.eu/imgs/J2000254_89903.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000254_89903.jpg,,,,,,"2016-02-12 08:36:52",,,,,,,,,,,,,,,,,"GTIN=4899888101352",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000254_89906.jpg,http://dropshipping.bigbuy.eu/imgs/J2000254_89905.jpg,http://dropshipping.bigbuy.eu/imgs/J2000254_89904.jpg","GTIN=4899888101352",,,,,,,,,, +BB-J2000285,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio) Beige L","<a id=""maggiorni_informazioni"" title=""OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio)""><p>Metti</a> in luce un corpo scultoreo con la <strong>Canottiera Modellante con Reggiseno Booby & Tummy! </strong>Discreta, comod. Sostiene il seno e offre una grande capacità di sostegno. Prodotta il un tessuto delicato, flessibile e traspirante che si adatta perfettamente al tuo corpo e offre una copertura completa (seno, fianchi e glutei).  Equivalenza di taglie: S: 36-38, M: 38-40, L: 40-42.</p><p><a href=""http://www.boobyandtummy.com/"" target=""_blank""><strong>www.boobyandtummy.com</strong></a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio): </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 13.5 Cm</li><li>Profondita': 7 Cm</li><li>Peso: 0.14 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888101352</p>","Metti in luce un corpo scultoreo con la Canottiera Modellante con Reggiseno Booby & Tummy! Discreta, comod.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio)""> Maggiori Informazioni</a>",0.14,1,"Taxable Goods","Catalog, Search",23.1,,,,OUTLET-Canottiera-Modellante-con-Reggiseno-Booby-&-Tummy--(Senza-imballaggio)-Beige-L,"OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio) Beige L","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Beige,Beige,Taglia L,L,","Metti in luce un corpo scultoreo con la Canottiera Modellante con Reggiseno Booby & Tummy! Discreta, comod",http://dropshipping.bigbuy.eu/imgs/J2000254_89906.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000254_89906.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=4899888101352",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000254_89903.jpg,http://dropshipping.bigbuy.eu/imgs/J2000254_89905.jpg,http://dropshipping.bigbuy.eu/imgs/J2000254_89904.jpg","GTIN=4899888101352",,,,,,,,,, +BB-J2000279,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio) Beige M","<a id=""maggiorni_informazioni"" title=""OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio)""><p>Metti</a> in luce un corpo scultoreo con la <strong>Canottiera Modellante con Reggiseno Booby & Tummy! </strong>Discreta, comod. Sostiene il seno e offre una grande capacità di sostegno. Prodotta il un tessuto delicato, flessibile e traspirante che si adatta perfettamente al tuo corpo e offre una copertura completa (seno, fianchi e glutei).  Equivalenza di taglie: S: 36-38, M: 38-40, L: 40-42.</p><p><a href=""http://www.boobyandtummy.com/"" target=""_blank""><strong>www.boobyandtummy.com</strong></a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio): </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 13.5 Cm</li><li>Profondita': 7 Cm</li><li>Peso: 0.14 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888101352</p>","Metti in luce un corpo scultoreo con la Canottiera Modellante con Reggiseno Booby & Tummy! Discreta, comod.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio)""> Maggiori Informazioni</a>",0.14,1,"Taxable Goods","Catalog, Search",23.1,,,,OUTLET-Canottiera-Modellante-con-Reggiseno-Booby-&-Tummy--(Senza-imballaggio)-Beige-M,"OUTLET Canottiera Modellante con Reggiseno Booby & Tummy (Senza imballaggio) Beige M","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Colore Beige,Beige,Taglia M,M,","Metti in luce un corpo scultoreo con la Canottiera Modellante con Reggiseno Booby & Tummy! Discreta, comod",http://dropshipping.bigbuy.eu/imgs/J2000254_89905.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000254_89905.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=4899888101352",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000254_89903.jpg,http://dropshipping.bigbuy.eu/imgs/J2000254_89906.jpg,http://dropshipping.bigbuy.eu/imgs/J2000254_89904.jpg","GTIN=4899888101352",,,,,,,,,, +BB-J2000264,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Scopa elettrica triangolare senza fili 360 Sweep (Senza imballaggio)","<a id=""maggiorni_informazioni"" title=""OUTLET Scopa elettrica triangolare senza fili 360 Sweep (Senza imballaggio)""><p>La <strong>scopa</a> elettrica triangolare 360 Sweep</strong><strong> </strong>è perfetta per pulire in modo facile, rapido ed efficace. Grazie alla sua tecnologia innovativa, le setole ruotano automaticamente per rimuovere a fondo tutto lo sporco. Inoltre, questa <strong>scopa elettrica</strong> è leggera e facile da usare, il che la rende molto comoda e pratica. La scopa elettrica Sweep ruota di 360º e raggiunge facilmente qualsiasi angolo di casa.</p><p>Prova la nuova scopa elettrica triangolare 360 Sweep e scopri un modo migliore per pulire!</p><p>Caratteristiche della Scopa elettrica triangolare 360 Sweep:</p><ul><li>Scopa elettrica senza fili</li><li>Setole rotanti</li><li>Bastone in alluminio removibile (c.ca 115cm)</li><li>Base piatta triangolare (3 lati identici: circa 33cm di lunghezza x 3cm di altezza)</li><li>Batteria ricaricabile  7.2V</li><li>Caricabatteria (230V, 50Hz)</li><li>Durata batteria: Approx. 30 min</li><li>Scopartimento interno raccogli-polvere</li><li>Leggera, facile da usare, svuotare e pulire</li><li>Estremamente silenziosa</li></ul><p> <a title=""Escoba Eléctrica Sweep 360"" href=""http://www.360sweep.com/"" target=""_blank"">www.360sweep.com</a></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Scopa elettrica triangolare senza fili 360 Sweep (Senza imballaggio): </br><ul><li>Altezza: 32 Cm</li><li>Larghezza: 39.5 Cm</li><li>Profondita': 9.5 Cm</li><li>Peso: 1.625 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888102458</p>","La scopa elettrica triangolare 360 Sweep è perfetta per pulire in modo facile, rapido ed efficace.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Scopa elettrica triangolare senza fili 360 Sweep (Senza imballaggio)""> Maggiori Informazioni</a>",1.625,1,"Taxable Goods","Catalog, Search",89.9,,,,OUTLET-Scopa-elettrica-triangolare-senza-fili-360-Sweep-(Senza-imballaggio),"OUTLET Scopa elettrica triangolare senza fili 360 Sweep (Senza imballaggio)","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,","La scopa elettrica triangolare 360 Sweep è perfetta per pulire in modo facile, rapido ed efficace",http://dropshipping.bigbuy.eu/imgs/J2000264_89527.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000264_89527.jpg,,,,,,"2016-09-01 06:15:11",,,,,,,,,,,,,,,,,"GTIN=4899888102458",2,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000264_89533.jpg,http://dropshipping.bigbuy.eu/imgs/J2000264_89532.jpg,http://dropshipping.bigbuy.eu/imgs/J2000264_89531.jpg,http://dropshipping.bigbuy.eu/imgs/J2000264_89530.jpg,http://dropshipping.bigbuy.eu/imgs/J2000264_89529.jpg,http://dropshipping.bigbuy.eu/imgs/J2000264_89528.jpg","GTIN=4899888102458",,,,,,,,,, +BB-J2000291,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Sabbia Kinetic per Bambini Playz Kidz (Senza imballaggio)","<a id=""maggiorni_informazioni"" title=""OUTLET Sabbia Kinetic per Bambini Playz Kidz (Senza imballaggio)""><p>Porta</a> la spiaggia a casa con la <strong>sabbia kinetic per bambini Playz Kidz</strong>! Il regalo perfetto per sorprendere i tuoi piccoli, che si divertiranno a costruire ogni tipo di castello e forma di sabbia. Questo divertente ed originale gioco sviluppa i talenti artistici e la creatività dei bambini. Comprende circa 0,5 kg di sabbia kinetic e 3 accessori in plastica: un rullo, una forcella e una spatola. Non tossico. Non macchia i vestiti o si attacca alle mani. Età consigliata: 3+ anni</p><p><strong><a href=""http://www.playzkidz.com"">www.playzkidz.com</a></strong></p><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Sabbia Kinetic per Bambini Playz Kidz (Senza imballaggio): </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 14 Cm</li><li>Profondita': 14 Cm</li><li>Peso: 0.65 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888108085</p>","Porta la spiaggia a casa con la sabbia kinetic per bambini Playz Kidz! Il regalo perfetto per sorprendere i tuoi piccoli, che si divertiranno a costruire ogni tipo di castello e forma di sabbia.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Sabbia Kinetic per Bambini Playz Kidz (Senza imballaggio)""> Maggiori Informazioni</a>",0.65,1,"Taxable Goods","Catalog, Search",11.9,,,,OUTLET-Sabbia-Kinetic-per-Bambini-Playz-Kidz--(Senza-imballaggio),"OUTLET Sabbia Kinetic per Bambini Playz Kidz (Senza imballaggio)","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,","Porta la spiaggia a casa con la sabbia kinetic per bambini Playz Kidz! Il regalo perfetto per sorprendere i tuoi piccoli, che si divertiranno a costruire ogni tipo di castello e forma di sabbia",http://dropshipping.bigbuy.eu/imgs/J2000291_90163.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000291_90163.jpg,,,,,,"2016-08-09 01:48:11",,,,,,,,,,,,,,,,,"GTIN=4899888108085",778,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000291_90165.jpg,http://dropshipping.bigbuy.eu/imgs/J2000291_90164.jpg","GTIN=4899888108085",,,,,,,,,, +BB-J2000350,,Default,simple,"Default Category/Outlet Offerte,Default Category/Outlet Offerte/Senza imballaggio",base,"OUTLET Reggiseno Crochet Bra (3 Pezzi) (Senza imballaggio) S","<a id=""maggiorni_informazioni"" title=""OUTLET Reggiseno Crochet Bra (3 Pezzi) (Senza imballaggio)""><p><strong>Acquista</a> il Reggiseno Crochet </strong>(3 pezzi)<strong> al miglior prezzo. </strong>Sentiti fantastica e sexy per tutto il giorno! Dimentica i fili, fascette o spalline. Il Crochet Bra utilizza la tecnologia <em>Woven Everlast</em> per un comfort massimo. Questi reggiseni sono stati elaborati senza tutti quegli elementi in modo da essere usato comodamente dimenticando che lo stai indossando. Questo reggiseno si adatta al tuo seno, sollevandolo e sostenendolo. Il Crochet Bra si adatta perfettamente al tuo corpo e forma , senza lasciare segni o pieghe. In più è morbido, flessibile e molto comodo, e si adatta ad ogni coppa, sollevando il tio seno in modo significativo.</p><p><a href=""http://www.crochetbra.com"" target=""_blank"">www.crochetbra.com</a><br /><strong><br />Caratteristiche:</strong></p><ul><li>Lavabile in lavatrice</li><li>Bretelle comode</li><li>Fatto al 96% di nylon e 4% di spandex</li><li>Adattabile alla forma del tuo seno</li><li>Solleva il tuo seno</li><li>La confezione include 3 reggiseni (1 beige, 1 nero and 1 bianco)</li><li>Equivalenza taglie appross.: S: 80/85 - M: 90/95 - L: 100/105</li></ul><p><strong>NOTA: Questo prodotto presenta lievi danni estetici, quali graffi o sfregature, che non influiscono sul suo funzionamento. Spedito in confezione standard.</strong></p><p> Dimenzioni per OUTLET Reggiseno Crochet Bra (3 Pezzi) (Senza imballaggio): </br><ul><li>Altezza: 4.5 Cm</li><li>Larghezza: 15.5 Cm</li><li>Profondita': 27 Cm</li><li>Peso: 0.269 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888101345</p>","Acquista il Reggiseno Crochet (3 pezzi) al miglior prezzo.</br><a href=""#maggiorni_informazioni"" title=""OUTLET Reggiseno Crochet Bra (3 Pezzi) (Senza imballaggio)""> Maggiori Informazioni</a>",0.269,1,"Taxable Goods","Catalog, Search",34.9,,,,OUTLET-Reggiseno-Crochet-Bra-(3-Pezzi)-(Senza-imballaggio)-S,"OUTLET Reggiseno Crochet Bra (3 Pezzi) (Senza imballaggio) S","Outlet Offerte,Outlet,Offerte,Senza imballaggio,Senza,imballaggio,Taglia S,S,","Acquista il Reggiseno Crochet (3 pezzi) al miglior prezzo",http://dropshipping.bigbuy.eu/imgs/J2000349_93435.jpg,,http://dropshipping.bigbuy.eu/imgs/J2000349_93435.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=4899888101345",8,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/J2000349_93438.jpg,http://dropshipping.bigbuy.eu/imgs/J2000349_93437.jpg,http://dropshipping.bigbuy.eu/imgs/J2000349_93436.jpg","GTIN=4899888101345",,,,,,,,,, diff --git a/dev/tests/acceptance/tests/_data/BB-ProductsWorking.csv b/dev/tests/acceptance/tests/_data/BB-ProductsWorking.csv new file mode 100644 index 0000000000000..0ea052a043526 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/BB-ProductsWorking.csv @@ -0,0 +1,29 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus +BB-D2010129,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Sistemi di Climatizzazione,Default Category/Casa Giardino/Sistemi di Climatizzazione/Aria condizionata e ventilatori",base,"Ventilatore Portatile Spray FunFan Nero","<a id=""maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""><p>Se</a> sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il <strong>ventilatore portatile spray FunFan</strong>. Si tratta di una soluzione pratica per mantenersi al fresco in una moltitudine di situazioni, come escursioni, gite in spiaggia, mentre si fa sport, ecc. Inoltre, grazie alle sue dimensioni ridotte (dimensioni: circa 9 x 26 x 6,5 cm) e peso ridotto (circa 130 g), lo puoi portare ovunque!<br /><br /><a href=""http://www.myfunfan.com""><strong>www.myfunfan.com</strong></a><br /><br />Questo <strong>ventilatore portatile</strong> originale ha un pulsante per attivare le eliche in PVC malleabili e una leva che spruzza l'acqua. Cosa c'è di più, puoi aggiungere il ghiaccio per aumentare la sensazione di freddo! Include 1 cacciavite a croce per inserire le batterie. Realizzato in PVC. Funzionamento a batterie (2 x AA, non incluse).</p><p> Dimenzioni per Ventilatore Portatile Spray FunFan : </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 28 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.185 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888101772</p>","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""> Maggiori Informazioni</a>",0.185,1,"Taxable Goods","Catalog, Search",19.9,,,,Ventilatore-Portatile-Spray-FunFan-Nero,"Ventilatore Portatile Spray FunFan Nero","Casa Giardino,Casa,Giardino,Sistemi di Climatizzazione,Sistemi,Climatizzazione,Aria condizionata e ventilatori,Aria,condizionata,ventilatori,Colore Nero,Nero,","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan",http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,,http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,,,,,,"2016-01-12 09:56:53",,,,,,,,,,,,,,,,,"GTIN=4899888101772",41,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78889.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78888.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78886.jpg","GTIN=4899888101772",,,,,,,,,, +BB-D2010130,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Sistemi di Climatizzazione,Default Category/Casa Giardino/Sistemi di Climatizzazione/Aria condizionata e ventilatori",base,"Ventilatore Portatile Spray FunFan Bianco","<a id=""maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""><p>Se</a> sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il <strong>ventilatore portatile spray FunFan</strong>. Si tratta di una soluzione pratica per mantenersi al fresco in una moltitudine di situazioni, come escursioni, gite in spiaggia, mentre si fa sport, ecc. Inoltre, grazie alle sue dimensioni ridotte (dimensioni: circa 9 x 26 x 6,5 cm) e peso ridotto (circa 130 g), lo puoi portare ovunque!<br /><br /><a href=""http://www.myfunfan.com""><strong>www.myfunfan.com</strong></a><br /><br />Questo <strong>ventilatore portatile</strong> originale ha un pulsante per attivare le eliche in PVC malleabili e una leva che spruzza l'acqua. Cosa c'è di più, puoi aggiungere il ghiaccio per aumentare la sensazione di freddo! Include 1 cacciavite a croce per inserire le batterie. Realizzato in PVC. Funzionamento a batterie (2 x AA, non incluse).</p><p> Dimenzioni per Ventilatore Portatile Spray FunFan : </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 28 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.185 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888107965</p>","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""> Maggiori Informazioni</a>",0.185,1,"Taxable Goods","Catalog, Search",19.9,,,,Ventilatore-Portatile-Spray-FunFan-Bianco,"Ventilatore Portatile Spray FunFan Bianco","Casa Giardino,Casa,Giardino,Sistemi di Climatizzazione,Sistemi,Climatizzazione,Aria condizionata e ventilatori,Aria,condizionata,ventilatori,Colore Bianco,Bianco,","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan",http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,,http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,,,,,,"2016-01-12 09:56:53",,,,,,,,,,,,,,,,,"GTIN=4899888107965",741,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78889.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78888.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78886.jpg","GTIN=4899888107965",,,,,,,,,, +BB-D2010131,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Sistemi di Climatizzazione,Default Category/Casa Giardino/Sistemi di Climatizzazione/Aria condizionata e ventilatori",base,"Ventilatore Portatile Spray FunFan Rosso","<a id=""maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""><p>Se</a> sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il <strong>ventilatore portatile spray FunFan</strong>. Si tratta di una soluzione pratica per mantenersi al fresco in una moltitudine di situazioni, come escursioni, gite in spiaggia, mentre si fa sport, ecc. Inoltre, grazie alle sue dimensioni ridotte (dimensioni: circa 9 x 26 x 6,5 cm) e peso ridotto (circa 130 g), lo puoi portare ovunque!<br /><br /><a href=""http://www.myfunfan.com""><strong>www.myfunfan.com</strong></a><br /><br />Questo <strong>ventilatore portatile</strong> originale ha un pulsante per attivare le eliche in PVC malleabili e una leva che spruzza l'acqua. Cosa c'è di più, puoi aggiungere il ghiaccio per aumentare la sensazione di freddo! Include 1 cacciavite a croce per inserire le batterie. Realizzato in PVC. Funzionamento a batterie (2 x AA, non incluse).</p><p> Dimenzioni per Ventilatore Portatile Spray FunFan : </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 28 Cm</li><li>Profondita': 7.5 Cm</li><li>Peso: 0.185 Kg</li></ul></p><p>Codice Prodotto (EAN): 4899888107972</p>","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore Portatile Spray FunFan ""> Maggiori Informazioni</a>",0.185,1,"Taxable Goods","Catalog, Search",19.9,,,,Ventilatore-Portatile-Spray-FunFan-Rosso,"Ventilatore Portatile Spray FunFan Rosso","Casa Giardino,Casa,Giardino,Sistemi di Climatizzazione,Sistemi,Climatizzazione,Aria condizionata e ventilatori,Aria,condizionata,ventilatori,Colore Rosso,Rosso,","Se sei il tipo di persona che è sempre alla ricerca di prodotti innovativi per combattere il caldo estivo, non perderti il ventilatore portatile spray FunFan",http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,,http://dropshipping.bigbuy.eu/imgs/D2010128_78890.jpg,,,,,,"2016-01-12 09:56:53",,,,,,,,,,,,,,,,,"GTIN=4899888107972",570,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/D2010128_78887.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78898.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78889.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78888.jpg,http://dropshipping.bigbuy.eu/imgs/D2010128_78886.jpg","GTIN=4899888107972",,,,,,,,,, +BB-H1000163,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Sedia Pieghevole Campart Travel CH0592 Blu Marino","<a id=""maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""><p>Se</a> stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la <strong>sedia pieghevole </strong><strong>Campart Travel</strong>! Questa <strong>sedia da campeggio imbottita</strong> è perfetta per i luoghi di campeggio, cortili, giardini, ecc. Ideale per il riposo e il relax. Può portare fino a 120 kg. Dimensioni: 66 x 70 / 120 x 87 / 115 cm circa. Semplice da trasportare ovunque, grazie al suo design funzionale ed elegante (dimensioni quando piegato: circa 66 x 110 x 10 cm). 7 posizioni regolabili e un poggiatesta incorporato. Struttura in alluminio e stoffa imbottita in poliestere. Altezza sedia: circa 50 cm.</p><p> Dimenzioni per Sedia Pieghevole Campart Travel: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 66 Cm</li><li>Profondita': 110 Cm</li><li>Peso: 5.3 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005922</p>","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc.</br><a href=""#maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""> Maggiori Informazioni</a>",5.3,1,"Taxable Goods","Catalog, Search",129,,,,Sedia-Pieghevole-Campart-Travel-CH0592 Blu Marino,"Sedia Pieghevole Campart Travel CH0592 Blu Marino","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0592 Blu Marino,CH0592 Blu Marino,","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc",http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_00.jpg,,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_00.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005922",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_02.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_04.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_03.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_01.jpg","GTIN=8713016005922",,,,,,,,,, +BB-H1000162,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Sedia Pieghevole Campart Travel CH0596 Grigio","<a id=""maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""><p>Se</a> stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la <strong>sedia pieghevole </strong><strong>Campart Travel</strong>! Questa <strong>sedia da campeggio imbottita</strong> è perfetta per i luoghi di campeggio, cortili, giardini, ecc. Ideale per il riposo e il relax. Può portare fino a 120 kg. Dimensioni: 66 x 70 / 120 x 87 / 115 cm circa. Semplice da trasportare ovunque, grazie al suo design funzionale ed elegante (dimensioni quando piegato: circa 66 x 110 x 10 cm). 7 posizioni regolabili e un poggiatesta incorporato. Struttura in alluminio e stoffa imbottita in poliestere. Altezza sedia: circa 50 cm.</p><p> Dimenzioni per Sedia Pieghevole Campart Travel: </br><ul><li>Altezza: 10 Cm</li><li>Larghezza: 66 Cm</li><li>Profondita': 110 Cm</li><li>Peso: 5.3 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005960</p>","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc.</br><a href=""#maggiorni_informazioni"" title=""Sedia Pieghevole Campart Travel""> Maggiori Informazioni</a>",5.3,1,"Taxable Goods","Catalog, Search",129,,,,Sedia-Pieghevole-Campart-Travel-CH0596 Grigio,"Sedia Pieghevole Campart Travel CH0596 Grigio","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0596 Grigio,CH0596 Grigio,","Se stai cercando comfort e convenienza allo stesso tempo, probabilmente adorerai la sedia pieghevole Campart Travel! Questa sedia da campeggio imbottita è perfetta per i luoghi di campeggio, cortili, giardini, ecc",http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_02.jpg,,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_02.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005960",2,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_00.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_04.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_03.jpg,http://dropshipping.bigbuy.eu/imgs/silla_plegable_camping_01.jpg","GTIN=8713016005960",,,,,,,,,, +BB-F1520329,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Poggiapiedi Pieghevole Campart Travel CH0593 Blu Marino","<a id=""maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""><p>Approfitta</a> di un'esperienza rilassante con l'aiuto del <strong>poggiapiedi pieghevole Campart Travel</strong>! Questo<strong> poggiapiedi imbottito</strong> è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc. Possiede 2 ganci di circa 3 cm di diametro che possono essere facilmente attaccate alla barra inferiore delle sedie (utilizzabile solo per sedie con una barra inferiore di circa 2 cm di diametro). Struttura in alluminio. Tessuto: poliestere. Dimensioni: circa 51 x 47 x 96 cm (dimensioni quando ripiegato: circa 51 x 12 x 96 cm). Ideale per le sedie pieghevoli Campart Travel CH0592 e CH0596.</p><p> Dimenzioni per Poggiapiedi Pieghevole Campart Travel: </br><ul><li>Altezza: 13 Cm</li><li>Larghezza: 53 Cm</li><li>Profondita': 97 Cm</li><li>Peso: 1.377 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005939</p>","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc.</br><a href=""#maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""> Maggiori Informazioni</a>",1.377,1,"Taxable Goods","Catalog, Search",46.6,,,,Poggiapiedi-Pieghevole-Campart-Travel-CH0593 Blu Marino,"Poggiapiedi Pieghevole Campart Travel CH0593 Blu Marino","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0593 Blu Marino,CH0593 Blu Marino,","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc",http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_00.jpg,,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_00.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005939",1,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_01.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_02.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_08.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_07.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_06.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_05.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_04.jpg","GTIN=8713016005939",,,,,,,,,, +BB-F1520328,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Decorazione, illuminazione e mobili",base,"Poggiapiedi Pieghevole Campart Travel CH0597 Grigio","<a id=""maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""><p>Approfitta</a> di un'esperienza rilassante con l'aiuto del <strong>poggiapiedi pieghevole Campart Travel</strong>! Questo<strong> poggiapiedi imbottito</strong> è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc. Possiede 2 ganci di circa 3 cm di diametro che possono essere facilmente attaccate alla barra inferiore delle sedie (utilizzabile solo per sedie con una barra inferiore di circa 2 cm di diametro). Struttura in alluminio. Tessuto: poliestere. Dimensioni: circa 51 x 47 x 96 cm (dimensioni quando ripiegato: circa 51 x 12 x 96 cm). Ideale per le sedie pieghevoli Campart Travel CH0592 e CH0596.</p><p> Dimenzioni per Poggiapiedi Pieghevole Campart Travel: </br><ul><li>Altezza: 13 Cm</li><li>Larghezza: 53 Cm</li><li>Profondita': 97 Cm</li><li>Peso: 1.377 Kg</li></ul></p><p>Codice Prodotto (EAN): 8713016005977</p>","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc.</br><a href=""#maggiorni_informazioni"" title=""Poggiapiedi Pieghevole Campart Travel""> Maggiori Informazioni</a>",1.377,1,"Taxable Goods","Catalog, Search",46.6,,,,Poggiapiedi-Pieghevole-Campart-Travel-CH0597 Grigio,"Poggiapiedi Pieghevole Campart Travel CH0597 Grigio","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Decorazione, illuminazione e mobili,Decorazione,,illuminazione,mobili,Referenza e Colore CH0597 Grigio,CH0597 Grigio,","Approfitta di un'esperienza rilassante con l'aiuto del poggiapiedi pieghevole Campart Travel! Questo poggiapiedi imbottito è l'accessorio ideale per sedie da cortile, terrazzo, giardini, luoghi da campeggio, ecc",http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_01.jpg,,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_01.jpg,,,,,,"2015-09-21 15:58:54",,,,,,,,,,,,,,,,,"GTIN=8713016005977",7,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_00.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_02.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_08.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_07.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_06.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_05.jpg,http://dropshipping.bigbuy.eu/imgs/reposapies_CH-0609_04.jpg","GTIN=8713016005977",,,,,,,,,, +BB-H4502058,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Orologi da parete e da tavolo",base,"Orologio da Parete Star Wars","<a id=""maggiorni_informazioni"" title=""Orologio da Parete Star Wars""><p>I</a> fan di Star Wars non potranno fare a meno di appendere l'<strong>orologio da parete Star Wars</strong> in casa loro! Realizzato in plastica. Funziona a batterie (1 x AA, non incluse). Diametro circa: 25,5 cm. Spessore circa: 3,5 cm.</p><p> Dimenzioni per Orologio da Parete Star Wars: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 26 Cm</li><li>Profondita': 3.8 Cm</li><li>Peso: 0.287 Kg</li></ul></p><p>Codice Prodotto (EAN): 6950687214204</p>","I fan di Star Wars non potranno fare a meno di appendere l'orologio da parete Star Wars in casa loro! Realizzato in plastica.</br><a href=""#maggiorni_informazioni"" title=""Orologio da Parete Star Wars""> Maggiori Informazioni</a>",0.287,1,"Taxable Goods","Catalog, Search",22.5,,,,Orologio-da-Parete-Star-Wars,"Orologio da Parete Star Wars","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Orologi da parete e da tavolo,Orologi,parete,tavolo,","I fan di Star Wars non potranno fare a meno di appendere l'orologio da parete Star Wars in casa loro! Realizzato in plastica",http://dropshipping.bigbuy.eu/imgs/H4502058_84712.jpg,,http://dropshipping.bigbuy.eu/imgs/H4502058_84712.jpg,,,,,,"2016-08-08 21:09:24",,,,,,,,,,,,,,,,,"GTIN=6950687214204",130,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/H4502058_84713.jpg,http://dropshipping.bigbuy.eu/imgs/H4502058_84711.jpg,http://dropshipping.bigbuy.eu/imgs/H4502058_84710.jpg","GTIN=6950687214204",,,,,,,,,, +BB-G0500195,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Illuminazione LED",base,"Braccialetto Sportivo a LED MegaLed Rosso","<a id=""maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""><p>Se</a> ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed. Con questo<strong> braccialetto di sicurezza</strong> sarai visibile ai motorini e alle auto nell'oscurità, così da poter stare molto più sicuro e tranquillo. È dotato di 2 luci a LED con 2 possibili soluzioni (luce fissa ed intermittente). La lunghezza massima è di circa 55 cm e quella minima è di circa 42 cm. Molto leggero (circa 50 g). Autonomia circa: 24-40 ore. Funziona a batterie (2 x CR2023, incluse).</p><p> </p><p> Dimenzioni per Braccialetto Sportivo a LED MegaLed: </br><ul><li>Altezza: 3 Cm</li><li>Larghezza: 20 Cm</li><li>Profondita': 4 Cm</li><li>Peso: 0.049 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436545443507</p>","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed.</br><a href=""#maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""> Maggiori Informazioni</a>",0.049,1,"Taxable Goods","Catalog, Search",22,,,,Braccialetto-Sportivo-a-LED-MegaLed-Rosso,"Braccialetto Sportivo a LED MegaLed Rosso","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Illuminazione LED,Illuminazione,LED,Colore Rosso,Rosso,","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed",http://dropshipping.bigbuy.eu/imgs/G0500194_87774.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500194_87774.jpg,,,,,,"2016-01-08 12:34:41",,,,,,,,,,,,,,,,,"GTIN=8436545443507",22,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500194_87775.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87773.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87772.jpg","GTIN=8436545443507",,,,,,,,,, +BB-G0500196,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Illuminazione LED",base,"Braccialetto Sportivo a LED MegaLed Verde","<a id=""maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""><p>Se</a> ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed. Con questo<strong> braccialetto di sicurezza</strong> sarai visibile ai motorini e alle auto nell'oscurità, così da poter stare molto più sicuro e tranquillo. È dotato di 2 luci a LED con 2 possibili soluzioni (luce fissa ed intermittente). La lunghezza massima è di circa 55 cm e quella minima è di circa 42 cm. Molto leggero (circa 50 g). Autonomia circa: 24-40 ore. Funziona a batterie (2 x CR2023, incluse).</p><p> </p><p> Dimenzioni per Braccialetto Sportivo a LED MegaLed: </br><ul><li>Altezza: 3 Cm</li><li>Larghezza: 20 Cm</li><li>Profondita': 4 Cm</li><li>Peso: 0.049 Kg</li></ul></p><p>Codice Prodotto (EAN): 8436545443507</p>","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed.</br><a href=""#maggiorni_informazioni"" title=""Braccialetto Sportivo a LED MegaLed""> Maggiori Informazioni</a>",0.049,1,"Taxable Goods","Catalog, Search",22,,,,Braccialetto-Sportivo-a-LED-MegaLed-Verde,"Braccialetto Sportivo a LED MegaLed Verde","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Illuminazione LED,Illuminazione,LED,Colore Verde,Verde,","Se ti piace praticare la corsa, il ciclismo, o qualunque sport all'aria aperta, non può mancare tra i tuoi accessori il braccialetto per lo sport a LED MegaLed",http://dropshipping.bigbuy.eu/imgs/G0500194_87775.jpg,,http://dropshipping.bigbuy.eu/imgs/G0500194_87775.jpg,,,,,,"2016-01-08 12:34:41",,,,,,,,,,,,,,,,,"GTIN=8436545443507",37,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/G0500194_87774.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87773.jpg,http://dropshipping.bigbuy.eu/imgs/G0500194_87772.jpg","GTIN=8436545443507",,,,,,,,,, +BB-I2500333,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Orologi da parete e da tavolo",base,"Orologio da Parete Mom's Diner","<a id=""maggiorni_informazioni"" title=""Orologio da Parete Mom's Diner""><p>Decora</a> la tua cucina con l'originale <strong>orologio da parete</strong> <strong>Mom's Diner</strong> in stile vintage! È realizzato in legno. Diametro: 58 cm circa. Spessore: 0,8 cm circa. Funziona a pile (1 x AA, non inclusa).</p><p> Dimenzioni per Orologio da Parete Mom's Diner: </br><ul><li>Altezza: 59 Cm</li><li>Larghezza: 59 Cm</li><li>Profondita': 6 Cm</li><li>Peso: 2.1 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811345052</p>","Decora la tua cucina con l'originale orologio da parete Mom's Diner in stile vintage! È realizzato in legno.</br><a href=""#maggiorni_informazioni"" title=""Orologio da Parete Mom's Diner""> Maggiori Informazioni</a>",2.1,1,"Taxable Goods","Catalog, Search",42.5,,,,Orologio-da-Parete-Mom's-Diner,"Orologio da Parete Mom's Diner","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Orologi da parete e da tavolo,Orologi,parete,tavolo,","Decora la tua cucina con l'originale orologio da parete Mom's Diner in stile vintage! È realizzato in legno",http://dropshipping.bigbuy.eu/imgs/I2500333_88061.jpg,,http://dropshipping.bigbuy.eu/imgs/I2500333_88061.jpg,,,,,,"2016-07-21 13:05:12",,,,,,,,,,,,,,,,,"GTIN=4029811345052",2,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I2500333_88060.jpg,http://dropshipping.bigbuy.eu/imgs/I2500333_88059.jpg,http://dropshipping.bigbuy.eu/imgs/I2500333_88058.jpg","GTIN=4029811345052",,,,,,,,,, +BB-I2500334,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Orologi da parete e da tavolo",base,"Orologio da Parete Coffee Endless Cup","<a id=""maggiorni_informazioni"" title=""Orologio da Parete Coffee Endless Cup""><p>Se</a> sei un appassionato di caffè, non puoi rimanere senza l'<strong>orologio da parete Coffee Endless Cup</strong>! Un orologio vintage in legno con un design in perfetto stile caffettoso! Diametro: 58 cm circa. Spessore: 0,8 cm circa. Funziona a pile (1 x AA, non inclusa).</p><p> Dimenzioni per Orologio da Parete Coffee Endless Cup: </br><ul><li>Altezza: 59 Cm</li><li>Larghezza: 59 Cm</li><li>Profondita': 6 Cm</li><li>Peso: 2.1 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811345069</p>","Se sei un appassionato di caffè, non puoi rimanere senza l'orologio da parete Coffee Endless Cup! Un orologio vintage in legno con un design in perfetto stile caffettoso! Diametro: 58 cm circa.</br><a href=""#maggiorni_informazioni"" title=""Orologio da Parete Coffee Endless Cup""> Maggiori Informazioni</a>",2.1,1,"Taxable Goods","Catalog, Search",42.5,,,,Orologio-da-Parete-Coffee-Endless-Cup,"Orologio da Parete Coffee Endless Cup","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Orologi da parete e da tavolo,Orologi,parete,tavolo,","Se sei un appassionato di caffè, non puoi rimanere senza l'orologio da parete Coffee Endless Cup! Un orologio vintage in legno con un design in perfetto stile caffettoso! Diametro: 58 cm circa",http://dropshipping.bigbuy.eu/imgs/I2500334_88065.jpg,,http://dropshipping.bigbuy.eu/imgs/I2500334_88065.jpg,,,,,,"2016-08-30 13:41:52",,,,,,,,,,,,,,,,,"GTIN=4029811345069",4,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/I2500334_88064.jpg,http://dropshipping.bigbuy.eu/imgs/I2500334_88063.jpg,http://dropshipping.bigbuy.eu/imgs/I2500334_88062.jpg","GTIN=4029811345069",,,,,,,,,, +BB-V0000252,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Insegna Dito Vintage Look Stop!","<a id=""maggiorni_informazioni"" title=""Insegna Dito Vintage Look""><p>Se</a> sei alla ricerca di una <strong>decorazione vintage</strong> originale e divertente per la tua casa, l'<strong>insegna dito Vintage Look</strong> ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno. Dimensioni: 69 x 17 x 1 cm circa.</p><p> Dimenzioni per Insegna Dito Vintage Look: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 69 Cm</li><li>Profondita': 1 Cm</li><li>Peso: 0.63 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346196</p>","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno.</br><a href=""#maggiorni_informazioni"" title=""Insegna Dito Vintage Look""> Maggiori Informazioni</a>",0.63,1,"Taxable Goods","Catalog, Search",15.99,,,,Insegna-Dito-Vintage-Look-Stop!,"Insegna Dito Vintage Look Stop!","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Stop!,Stop!,","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno",http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,,,,,,"2016-02-29 09:49:10",,,,,,,,,,,,,,,,,"GTIN=4029811346196",21,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89743.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89742.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89741.jpg","GTIN=4029811346196",,,,,,,,,, +BB-V0000253,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Insegna Dito Vintage Look Adults Only","<a id=""maggiorni_informazioni"" title=""Insegna Dito Vintage Look""><p>Se</a> sei alla ricerca di una <strong>decorazione vintage</strong> originale e divertente per la tua casa, l'<strong>insegna dito Vintage Look</strong> ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno. Dimensioni: 69 x 17 x 1 cm circa.</p><p> Dimenzioni per Insegna Dito Vintage Look: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 69 Cm</li><li>Profondita': 1 Cm</li><li>Peso: 0.63 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346202</p>","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno.</br><a href=""#maggiorni_informazioni"" title=""Insegna Dito Vintage Look""> Maggiori Informazioni</a>",0.63,1,"Taxable Goods","Catalog, Search",15.99,,,,Insegna-Dito-Vintage-Look-Adults Only,"Insegna Dito Vintage Look Adults Only","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Adults Only,Adults Only,","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno",http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,,,,,,"2016-02-29 09:49:10",,,,,,,,,,,,,,,,,"GTIN=4029811346202",18,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89743.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89742.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89741.jpg","GTIN=4029811346202",,,,,,,,,, +BB-V0000254,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Insegna Dito Vintage Look Talk","<a id=""maggiorni_informazioni"" title=""Insegna Dito Vintage Look""><p>Se</a> sei alla ricerca di una <strong>decorazione vintage</strong> originale e divertente per la tua casa, l'<strong>insegna dito Vintage Look</strong> ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno. Dimensioni: 69 x 17 x 1 cm circa.</p><p> Dimenzioni per Insegna Dito Vintage Look: </br><ul><li>Altezza: 17 Cm</li><li>Larghezza: 69 Cm</li><li>Profondita': 1 Cm</li><li>Peso: 0.63 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346318</p>","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno.</br><a href=""#maggiorni_informazioni"" title=""Insegna Dito Vintage Look""> Maggiori Informazioni</a>",0.63,1,"Taxable Goods","Catalog, Search",15.99,,,,Insegna-Dito-Vintage-Look-Talk,"Insegna Dito Vintage Look Talk","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Talk,Talk,","Se sei alla ricerca di una decorazione vintage originale e divertente per la tua casa, l'insegna dito Vintage Look ti conquisterà con il suo stile e i suoi simpatici messaggi! Realizzata in legno",http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000251_89744.jpg,,,,,,"2016-02-29 09:49:10",,,,,,,,,,,,,,,,,"GTIN=4029811346318",8,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000251_89745.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89746.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89743.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89742.jpg,http://dropshipping.bigbuy.eu/imgs/V0000251_89741.jpg","GTIN=4029811346318",,,,,,,,,, +BB-V0000256,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Freccia Decorativa Vintage Look Go Left","<a id=""maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""><p>Stupisci</a> tutti con la divertente ed originale <strong>freccia decorativa Vintage Look</strong>! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno. Misure appross.: 25 x 40 x 1 cm.</p><p> Dimenzioni per Freccia Decorativa Vintage Look: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 0.8 Cm</li><li>Profondita': 40 Cm</li><li>Peso: 0.376 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346325</p>","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno.</br><a href=""#maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""> Maggiori Informazioni</a>",0.376,1,"Taxable Goods","Catalog, Search",13.9,,,,Freccia-Decorativa-Vintage-Look-Go Left,"Freccia Decorativa Vintage Look Go Left","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Go Left,Go Left,","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno",http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,,,,,,"2016-02-29 10:39:59",,,,,,,,,,,,,,,,,"GTIN=4029811346325",4,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89759.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89758.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89757.jpg","GTIN=4029811346325",,,,,,,,,, +BB-V0000257,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Freccia Decorativa Vintage Look Exit","<a id=""maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""><p>Stupisci</a> tutti con la divertente ed originale <strong>freccia decorativa Vintage Look</strong>! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno. Misure appross.: 25 x 40 x 1 cm.</p><p> Dimenzioni per Freccia Decorativa Vintage Look: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 0.8 Cm</li><li>Profondita': 40 Cm</li><li>Peso: 0.376 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346332</p>","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno.</br><a href=""#maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""> Maggiori Informazioni</a>",0.376,1,"Taxable Goods","Catalog, Search",13.9,,,,Freccia-Decorativa-Vintage-Look-Exit,"Freccia Decorativa Vintage Look Exit","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Exit,Exit,","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno",http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,,,,,,"2016-02-29 10:39:59",,,,,,,,,,,,,,,,,"GTIN=4029811346332",19,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89759.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89758.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89757.jpg","GTIN=4029811346332",,,,,,,,,, +BB-V0000258,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Altri articoli decorativi",base,"Freccia Decorativa Vintage Look Cold beer here","<a id=""maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""><p>Stupisci</a> tutti con la divertente ed originale <strong>freccia decorativa Vintage Look</strong>! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno. Misure appross.: 25 x 40 x 1 cm.</p><p> Dimenzioni per Freccia Decorativa Vintage Look: </br><ul><li>Altezza: 25.5 Cm</li><li>Larghezza: 0.8 Cm</li><li>Profondita': 40 Cm</li><li>Peso: 0.376 Kg</li></ul></p><p>Codice Prodotto (EAN): 4029811346349</p>","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno.</br><a href=""#maggiorni_informazioni"" title=""Freccia Decorativa Vintage Look""> Maggiori Informazioni</a>",0.376,1,"Taxable Goods","Catalog, Search",13.9,,,,Freccia-Decorativa-Vintage-Look-Cold beer here,"Freccia Decorativa Vintage Look Cold beer here","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Altri articoli decorativi,Altri,articoli,decorativi,Design Cold beer here,Cold beer here,","Stupisci tutti con la divertente ed originale freccia decorativa Vintage Look! Le pareti di casa tua non resteranno indifferenti a nessuno! Fabbricata in legno",http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,,http://dropshipping.bigbuy.eu/imgs/V0000255_89760.jpg,,,,,,"2016-02-29 10:39:59",,,,,,,,,,,,,,,,,"GTIN=4029811346349",20,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0000255_89756.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89761.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89759.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89758.jpg,http://dropshipping.bigbuy.eu/imgs/V0000255_89757.jpg","GTIN=4029811346349",,,,,,,,,, +BB-V0200190,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Centrotavola e Vasi",base,"Ciotola in Bambù TakeTokio Bianco","<a id=""maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""><p>Arricchisci</a> la selezione dei tuoi <strong>utensili da cucina</strong> con la <strong>ciotola in bambù</strong> <strong>TakeTokio</strong>, una <strong>ciotola da cucina</strong> funzionale e dal design moderno è perfetta come <strong>insalatiera</strong>, portafrutta, ecc. Realizzata in pregiato legno di bambù. Dimensioni (diametro x altezza): 25 x 15 cm circa. Diametro della base: 9 cm circa.</p><p><a href=""http://www.taketokio.com/"" target=""_blank""><strong>www.taketokio.com</strong></a></p><p> Dimenzioni per Ciotola in Bambù TakeTokio: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 15 Cm</li><li>Peso: 0.39 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158904577</p>","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc.</br><a href=""#maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""> Maggiori Informazioni</a>",0.39,1,"Taxable Goods","Catalog, Search",19.8,,,,Ciotola-in-Bambù-TakeTokio-Bianco,"Ciotola in Bambù TakeTokio Bianco","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Centrotavola e Vasi,Centrotavola,Vasi,Colore Bianco,Bianco,","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc",http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8718158904577",0,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90744.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90743.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90742.jpg","GTIN=8718158904577",,,,,,,,,, +BB-V0200192,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Centrotavola e Vasi",base,"Ciotola in Bambù TakeTokio Grigio","<a id=""maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""><p>Arricchisci</a> la selezione dei tuoi <strong>utensili da cucina</strong> con la <strong>ciotola in bambù</strong> <strong>TakeTokio</strong>, una <strong>ciotola da cucina</strong> funzionale e dal design moderno è perfetta come <strong>insalatiera</strong>, portafrutta, ecc. Realizzata in pregiato legno di bambù. Dimensioni (diametro x altezza): 25 x 15 cm circa. Diametro della base: 9 cm circa.</p><p><a href=""http://www.taketokio.com/"" target=""_blank""><strong>www.taketokio.com</strong></a></p><p> Dimenzioni per Ciotola in Bambù TakeTokio: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 15 Cm</li><li>Peso: 0.39 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158904577</p>","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc.</br><a href=""#maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""> Maggiori Informazioni</a>",0.39,1,"Taxable Goods","Catalog, Search",19.8,,,,Ciotola-in-Bambù-TakeTokio-Grigio,"Ciotola in Bambù TakeTokio Grigio","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Centrotavola e Vasi,Centrotavola,Vasi,Colore Grigio,Grigio,","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc",http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8718158904577",26,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90744.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90743.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90742.jpg","GTIN=8718158904577",,,,,,,,,, +BB-V0200191,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Decorazione e Illuminazione,Default Category/Casa Giardino/Decorazione e Illuminazione/Centrotavola e Vasi",base,"Ciotola in Bambù TakeTokio Nero","<a id=""maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""><p>Arricchisci</a> la selezione dei tuoi <strong>utensili da cucina</strong> con la <strong>ciotola in bambù</strong> <strong>TakeTokio</strong>, una <strong>ciotola da cucina</strong> funzionale e dal design moderno è perfetta come <strong>insalatiera</strong>, portafrutta, ecc. Realizzata in pregiato legno di bambù. Dimensioni (diametro x altezza): 25 x 15 cm circa. Diametro della base: 9 cm circa.</p><p><a href=""http://www.taketokio.com/"" target=""_blank""><strong>www.taketokio.com</strong></a></p><p> Dimenzioni per Ciotola in Bambù TakeTokio: </br><ul><li>Altezza: 25 Cm</li><li>Larghezza: 25 Cm</li><li>Profondita': 15 Cm</li><li>Peso: 0.39 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158904577</p>","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc.</br><a href=""#maggiorni_informazioni"" title=""Ciotola in Bambù TakeTokio""> Maggiori Informazioni</a>",0.39,1,"Taxable Goods","Catalog, Search",19.8,,,,Ciotola-in-Bambù-TakeTokio-Nero,"Ciotola in Bambù TakeTokio Nero","Casa Giardino,Casa,Giardino,Decorazione e Illuminazione,Decorazione,Illuminazione,Centrotavola e Vasi,Centrotavola,Vasi,Colore Nero,Nero,","Arricchisci la selezione dei tuoi utensili da cucina con la ciotola in bambù TakeTokio, una ciotola da cucina funzionale e dal design moderno è perfetta come insalatiera, portafrutta, ecc",http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200189_90745.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8718158904577",22,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200189_90746.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90747.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90744.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90743.jpg,http://dropshipping.bigbuy.eu/imgs/V0200189_90742.jpg","GTIN=8718158904577",,,,,,,,,, +BB-V0200360,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Scatola porta Tè Flower Vintage Coconut Rosa","<a id=""maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""><p>Gli</a> amanti della moda vintage non potranno resistere di fronte all'adorabile <strong>scatola porta tè Flower Vintage Coconut</strong>! Una <strong>scatola vintage</strong> in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi. Dispone di un coperchio in cristallo e vari scompartimenti. Dimensioni: circa 23 x 7 x 23 cm.</p><p><a href=""http://www.vintagecoconut.com"" target=""_blank""><strong>www.vintagecoconut.com</strong></a></p><p> Dimenzioni per Scatola porta Tè Flower Vintage Coconut: </br><ul><li>Altezza: 23 Cm</li><li>Larghezza: 7.1 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.795 Kg</li></ul></p><p>Codice Prodotto (EAN): 8711295889547</p>","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi.</br><a href=""#maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""> Maggiori Informazioni</a>",0.795,1,"Taxable Goods","Catalog, Search",25.9,,,,Scatola-porta-Tè-Flower-Vintage-Coconut-Rosa,"Scatola porta Tè Flower Vintage Coconut Rosa","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,Colore Rosa,Rosa,","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi",http://dropshipping.bigbuy.eu/imgs/V0200328_92649.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200328_92649.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8711295889547",13,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200328_92648.jpg,http://dropshipping.bigbuy.eu/imgs/V0200328_92647.jpg","GTIN=8711295889547",,,,,,,,,, +BB-V0200361,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Scatola porta Tè Flower Vintage Coconut Azzurro","<a id=""maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""><p>Gli</a> amanti della moda vintage non potranno resistere di fronte all'adorabile <strong>scatola porta tè Flower Vintage Coconut</strong>! Una <strong>scatola vintage</strong> in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi. Dispone di un coperchio in cristallo e vari scompartimenti. Dimensioni: circa 23 x 7 x 23 cm.</p><p><a href=""http://www.vintagecoconut.com"" target=""_blank""><strong>www.vintagecoconut.com</strong></a></p><p> Dimenzioni per Scatola porta Tè Flower Vintage Coconut: </br><ul><li>Altezza: 23 Cm</li><li>Larghezza: 7.1 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.795 Kg</li></ul></p><p>Codice Prodotto (EAN): 8711295889547</p>","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi.</br><a href=""#maggiorni_informazioni"" title=""Scatola porta Tè Flower Vintage Coconut""> Maggiori Informazioni</a>",0.795,1,"Taxable Goods","Catalog, Search",25.9,,,,Scatola-porta-Tè-Flower-Vintage-Coconut-Azzurro,"Scatola porta Tè Flower Vintage Coconut Azzurro","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,Colore Azzurro,Azzurro,","Gli amanti della moda vintage non potranno resistere di fronte all'adorabile scatola porta tè Flower Vintage Coconut! Una scatola vintage in legno ottima come decorazione e utilissima per sistemare le bustine di tè o di altri infusi",http://dropshipping.bigbuy.eu/imgs/V0200328_92648.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200328_92648.jpg,,,,,,"-0001-11-30 00:00:00",,,,,,,,,,,,,,,,,"GTIN=8711295889547",21,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200328_92649.jpg,http://dropshipping.bigbuy.eu/imgs/V0200328_92647.jpg","GTIN=8711295889547",,,,,,,,,, +BB-V0200353,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Giardino e Terrazza,Default Category/Casa Giardino/Giardino e Terrazza/Barbecue",base,"Ventilatore a Pistola classico per Barbecue BBQ Classics","<a id=""maggiorni_informazioni"" title=""Ventilatore a Pistola classico per Barbecue BBQ Classics""><p>Utilizza</a> i migliori barbecue alimentando il fuoco con il <strong>ventilatore a pistola classico per babecue BBQ Classics</strong>! Basterà solo fare una leggera pressione sul pulsante per far uscire l'aria.</p><p><a href=""http://www.bbqclassics.com"" target=""_blank""><strong>www.bbqclassics.com</strong></a></p><ul><li>Realizzato in plastica e metallo</li><li>Dimensioni: 25 x 18 x 4 cm circa</li></ul><p> Dimenzioni per Ventilatore a Pistola classico per Barbecue BBQ Classics: </br><ul><li>Altezza: 5.5 Cm</li><li>Larghezza: 11 Cm</li><li>Profondita': 22 Cm</li><li>Peso: 0.167 Kg</li></ul></p><p>Codice Prodotto (EAN): 8718158032706</p>","Utilizza i migliori barbecue alimentando il fuoco con il ventilatore a pistola classico per babecue BBQ Classics! Basterà solo fare una leggera pressione sul pulsante per far uscire l'aria.</br><a href=""#maggiorni_informazioni"" title=""Ventilatore a Pistola classico per Barbecue BBQ Classics""> Maggiori Informazioni</a>",0.167,1,"Taxable Goods","Catalog, Search",9.3,,,,Ventilatore-a-Pistola-classico-per-Barbecue-BBQ-Classics,"Ventilatore a Pistola classico per Barbecue BBQ Classics","Casa Giardino,Casa,Giardino,Giardino e Terrazza,Giardino,Terrazza,Barbecue,","Utilizza i migliori barbecue alimentando il fuoco con il ventilatore a pistola classico per babecue BBQ Classics! Basterà solo fare una leggera pressione sul pulsante per far uscire l'aria",http://dropshipping.bigbuy.eu/imgs/V0200353_92695.jpg,,http://dropshipping.bigbuy.eu/imgs/V0200353_92695.jpg,,,,,,"2016-09-13 09:56:07",,,,,,,,,,,,,,,,,"GTIN=8718158032706",60,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V0200353_92696.jpg,http://dropshipping.bigbuy.eu/imgs/V0200353_92694.jpg","GTIN=8718158032706",,,,,,,,,, +BB-V1600123,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Frozen (32 x 23 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (32 x 23 cm)""><p>Non</a> c'è nulla di meglio che tenere le camere dei più piccini in ordine in modo originale e divertente. Con il <strong>contenitore portagiochi Frozen (32 x 23 cm)</strong> sarà semplicissimo!</p><ul><li>Realizzato in polipropilene</li><li>Dimensioni aprossimative: 32 x 15 x 23 cm</li><li>Età consigliata: +3 anni</li></ul><p> </p><p> Dimenzioni per Contenitore Portagiochi Frozen (32 x 23 cm): </br><ul><li>Altezza: 15 Cm</li><li>Larghezza: 34 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.331 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766006</p>","Non c'è nulla di meglio che tenere le camere dei più piccini in ordine in modo originale e divertente.</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (32 x 23 cm)""> Maggiori Informazioni</a>",0.331,1,"Taxable Goods","Catalog, Search",21.9,,,,Contenitore-Portagiochi-Frozen-(32-x-23-cm),"Contenitore Portagiochi Frozen (32 x 23 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","Non c'è nulla di meglio che tenere le camere dei più piccini in ordine in modo originale e divertente",http://dropshipping.bigbuy.eu/imgs/V1600123_93002.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600123_93002.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8412842766006",48,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600123_93005.jpg,http://dropshipping.bigbuy.eu/imgs/V1600123_93004.jpg,http://dropshipping.bigbuy.eu/imgs/V1600123_93003.jpg","GTIN=8412842766006",,,,,,,,,, +BB-V1600124,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Spiderman (32 x 23 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (32 x 23 cm)""><p>Desideri</a> sorprendere i più piccini con un regalo molto originale? Il <strong>contenitore portagiochi Spiderman (32 x 23 cm)</strong> decorerà e metterà in ordine le loro camerette.</p><ul><li>Realizzato in polipropilene</li><li>Dimensioni approssimative: 32 x 15 x 23 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Contenitore Portagiochi Spiderman (32 x 23 cm): </br><ul><li>Altezza: 15 Cm</li><li>Larghezza: 34 Cm</li><li>Profondita': 23 Cm</li><li>Peso: 0.331 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766037</p>","Desideri sorprendere i più piccini con un regalo molto originale? Il contenitore portagiochi Spiderman (32 x 23 cm) decorerà e metterà in ordine le loro camerette.</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (32 x 23 cm)""> Maggiori Informazioni</a>",0.331,1,"Taxable Goods","Catalog, Search",21.9,,,,Contenitore-Portagiochi-Spiderman--(32-x-23-cm),"Contenitore Portagiochi Spiderman (32 x 23 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","Desideri sorprendere i più piccini con un regalo molto originale? Il contenitore portagiochi Spiderman (32 x 23 cm) decorerà e metterà in ordine le loro camerette",http://dropshipping.bigbuy.eu/imgs/V1600124_93006.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600124_93006.jpg,,,,,,"2016-08-08 21:09:24",,,,,,,,,,,,,,,,,"GTIN=8412842766037",52,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600124_93008.jpg,http://dropshipping.bigbuy.eu/imgs/V1600124_93007.jpg","GTIN=8412842766037",,,,,,,,,, +BB-V1600125,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Frozen (45 x 32 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (45 x 32 cm)""><p>Insegna</a> ai tuoi bambini a tenere i giocattoli conservati ben in ordine con l'aiuto del <strong>contenitore portagiochi Frozen (45 x 32 cm)</strong>. Il <strong>portagiocattoli</strong> che tutte le bambine sognano!</p><ul><li>Realizzato in polipropilene</li><li>Dimensioni: circa 45 x 22 x 32 cm</li><li>Età consigliata: +3 anni</li></ul><p> Dimenzioni per Contenitore Portagiochi Frozen (45 x 32 cm): </br><ul><li>Altezza: 22 Cm</li><li>Larghezza: 37 Cm</li><li>Profondita': 45 Cm</li><li>Peso: 0.775 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766129</p>","Insegna ai tuoi bambini a tenere i giocattoli conservati ben in ordine con l'aiuto del contenitore portagiochi Frozen (45 x 32 cm).</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Frozen (45 x 32 cm)""> Maggiori Informazioni</a>",0.775,1,"Taxable Goods","Catalog, Search",39.6,,,,Contenitore-Portagiochi-Frozen-(45-x-32-cm),"Contenitore Portagiochi Frozen (45 x 32 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","Insegna ai tuoi bambini a tenere i giocattoli conservati ben in ordine con l'aiuto del contenitore portagiochi Frozen (45 x 32 cm)",http://dropshipping.bigbuy.eu/imgs/V1600125_93010.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600125_93010.jpg,,,,,,"2016-08-08 21:04:27",,,,,,,,,,,,,,,,,"GTIN=8412842766129",17,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600125_93011.jpg,http://dropshipping.bigbuy.eu/imgs/V1600125_93009.jpg","GTIN=8412842766129",,,,,,,,,, +BB-V1600126,,Default,simple,"Default Category/Casa Giardino,Default Category/Casa Giardino/Arredamento,Default Category/Casa Giardino/Arredamento/Soluzioni per Organizzare",base,"Contenitore Portagiochi Spiderman (45 x 32 cm)","<a id=""maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (45 x 32 cm)""><p>I</a> piccoli di casa ora possono ordinare e riporre i loro giocattoli facilmente grazie al <strong>c<strong>ontenitore <strong>portagiochi</strong></strong> Spiderman</strong><strong> (45 x 32 cm)</strong>. Il <strong>c<strong>ontenitore <strong>portagiochi</strong></strong> </strong>preferito dai bambini!</p><ul><li>Fabbricato in polipropilene</li><li>Dimensioni: circa 45 x 22 x 32 cm</li><li>Età raccomandata: +3 anni</li></ul><p> Dimenzioni per Contenitore Portagiochi Spiderman (45 x 32 cm): </br><ul><li>Altezza: 22 Cm</li><li>Larghezza: 37 Cm</li><li>Profondita': 45 Cm</li><li>Peso: 0.775 Kg</li></ul></p><p>Codice Prodotto (EAN): 8412842766150</p>","I piccoli di casa ora possono ordinare e riporre i loro giocattoli facilmente grazie al contenitore portagiochi Spiderman (45 x 32 cm).</br><a href=""#maggiorni_informazioni"" title=""Contenitore Portagiochi Spiderman (45 x 32 cm)""> Maggiori Informazioni</a>",0.775,1,"Taxable Goods","Catalog, Search",39.6,,,,Contenitore-Portagiochi-Spiderman-(45-x-32-cm),"Contenitore Portagiochi Spiderman (45 x 32 cm)","Casa Giardino,Casa,Giardino,Arredamento,Soluzioni per Organizzare,Soluzioni,Organizzare,","I piccoli di casa ora possono ordinare e riporre i loro giocattoli facilmente grazie al contenitore portagiochi Spiderman (45 x 32 cm)",http://dropshipping.bigbuy.eu/imgs/V1600126_93012.jpg,,http://dropshipping.bigbuy.eu/imgs/V1600126_93012.jpg,,,,,,"2016-08-08 21:09:24",,,,,,,,,,,,,,,,,"GTIN=8412842766150",18,0,1,0,1,1,1,1,10000,1,1,,1,0,1,1,1,1,0,0,0,,,,,,,"http://dropshipping.bigbuy.eu/imgs/V1600126_93014.jpg,http://dropshipping.bigbuy.eu/imgs/V1600126_93013.jpg","GTIN=8412842766150",,,,,,,,,, From ee9b46dbe7d8653ce770d06e97b6a31016092bc0 Mon Sep 17 00:00:00 2001 From: Aliaksei Yakimovich2 <aliaksei_yakimovich2@epam.com> Date: Tue, 25 Jun 2019 16:25:32 +0300 Subject: [PATCH 078/841] MAGETWO-70885: [SOAP] The 'incrementId' property of the order with state 'complete' is increased after order status update - Added api-functional test; --- .../Sales/Service/V1/OrderUpdateTest.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php new file mode 100644 index 0000000000000..212f1f2c12af7 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Service\V1; + +use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\Sales\Api\Data\OrderInterface; + +/** + * Test order updating via webapi + */ +class OrderUpdateTest extends WebapiAbstract +{ + const RESOURCE_PATH = '/V1/orders'; + + const SERVICE_NAME = 'salesOrderRepositoryV1'; + + const SERVICE_VERSION = 'V1'; + + const ORDER_INCREMENT_ID = '100000001'; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + protected $objectManager; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * Check order increment id after updating via webapi + * + * @magentoApiDataFixture Magento/Sales/_files/order.php + */ + public function testOrderUpdate() + { + /** @var \Magento\Sales\Model\Order $order */ + $order = $this->objectManager->get(\Magento\Sales\Model\Order::class) + ->loadByIncrementId(self::ORDER_INCREMENT_ID); + + $entityData = [ + OrderInterface::ENTITY_ID => $order->getId(), + OrderInterface::STATE => 'processing', + OrderInterface::STATUS => 'processing' + ]; + $requestData = ['entity' => $entityData]; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/orders', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'save', + ], + ]; + $result = $this->_webApiCall($serviceInfo, $requestData); + $this->assertGreaterThan(1, count($result)); + + /** @var \Magento\Sales\Model\Order $actualOrder */ + $actualOrder = $this->objectManager->get(\Magento\Sales\Model\Order::class)->load($order->getId()); + $this->assertEquals( + $order->getData(OrderInterface::INCREMENT_ID), + $actualOrder->getData(OrderInterface::INCREMENT_ID) + ); + } +} From d4f0f25be66f04dcba68c5d8dcb77e8b686e8f96 Mon Sep 17 00:00:00 2001 From: Lars Roettig <l.roettig@techdivision.com> Date: Tue, 25 Jun 2019 22:45:10 +0200 Subject: [PATCH 079/841] #685: Refactoring to CustomerDownloadableGraphQl - WIP --- .../Resolver/CustomerDownloadableProducts.php | 2 +- .../CustomerDownloadableGraphQl/README.md | 4 +++ .../Test/Mftf/README.md | 3 ++ .../CustomerDownloadableGraphQl/composer.json | 28 +++++++++++++++++++ .../etc/module.xml | 18 ++++++++++++ .../etc/schema.graphqls | 18 ++++++++++++ .../registration.php | 9 ++++++ .../DownloadableGraphQl/etc/schema.graphqls | 16 ----------- 8 files changed, 81 insertions(+), 17 deletions(-) rename app/code/Magento/{DownloadableGraphQl => CustomerDownloadableGraphQl}/Model/Resolver/CustomerDownloadableProducts.php (97%) create mode 100644 app/code/Magento/CustomerDownloadableGraphQl/README.md create mode 100644 app/code/Magento/CustomerDownloadableGraphQl/Test/Mftf/README.md create mode 100644 app/code/Magento/CustomerDownloadableGraphQl/composer.json create mode 100644 app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml create mode 100644 app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls create mode 100644 app/code/Magento/CustomerDownloadableGraphQl/registration.php diff --git a/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php b/app/code/Magento/CustomerDownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php similarity index 97% rename from app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php rename to app/code/Magento/CustomerDownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php index cb7a6aff2f451..da5c6cf794bf5 100644 --- a/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php +++ b/app/code/Magento/CustomerDownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\DownloadableGraphQl\Model\Resolver; +namespace Magento\CustomerDownloadableGraphQl\Model\Resolver; use Magento\DownloadableGraphQl\Model\ResourceModel\GetPurchasedDownloadableProducts; use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; diff --git a/app/code/Magento/CustomerDownloadableGraphQl/README.md b/app/code/Magento/CustomerDownloadableGraphQl/README.md new file mode 100644 index 0000000000000..044eeaf768a92 --- /dev/null +++ b/app/code/Magento/CustomerDownloadableGraphQl/README.md @@ -0,0 +1,4 @@ +# DownloadableGraphQl + +**CustomerDownloadableGraphQl** provides type and resolver information for the GraphQl module +to generate downloadable product information. diff --git a/app/code/Magento/CustomerDownloadableGraphQl/Test/Mftf/README.md b/app/code/Magento/CustomerDownloadableGraphQl/Test/Mftf/README.md new file mode 100644 index 0000000000000..51a682e004f58 --- /dev/null +++ b/app/code/Magento/CustomerDownloadableGraphQl/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Downloadable Graph Ql Functional Tests + +The Functional Test Module for **Magento Customer Downloadable Graph Ql** module. diff --git a/app/code/Magento/CustomerDownloadableGraphQl/composer.json b/app/code/Magento/CustomerDownloadableGraphQl/composer.json new file mode 100644 index 0000000000000..cb9b8bb89d53e --- /dev/null +++ b/app/code/Magento/CustomerDownloadableGraphQl/composer.json @@ -0,0 +1,28 @@ +{ + "name": "magento/module-customer-downloadable-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0", + "magento/module-catalog": "*", + "magento/module-downloadable": "*", + "magento/module-downloadable-graph-ql": "*", + "magento/module-graph-ql": "*", + "magento/framework": "*" + }, + "suggest": { + "magento/module-catalog-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\DownloadableGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml new file mode 100644 index 0000000000000..6dc201eb00ef9 --- /dev/null +++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_CustomerDownloadableGraphQl" > + <sequence> + <module name="Magento_Catalog"/> + <module name="Magento_Downloadable"/> + <module name="Magento_GraphQl"/> + <module name="Magento_CatalogGraphQl"/> + <module name="Magento_DownloadableGraphQl"/> + </sequence> + </module> +</config> diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls new file mode 100644 index 0000000000000..d4544605f241d --- /dev/null +++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls @@ -0,0 +1,18 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type Query { + customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false) +} + +type CustomerDownloadableProducts { + items: [CustomerDownloadableProduct] @doc(description: "List of purchased downloadable items") +} + +type CustomerDownloadableProduct { + order_increment_id: String + date: String + status: String + download_url: String + remaining_downloads: String +} \ No newline at end of file diff --git a/app/code/Magento/CustomerDownloadableGraphQl/registration.php b/app/code/Magento/CustomerDownloadableGraphQl/registration.php new file mode 100644 index 0000000000000..3308d79f92458 --- /dev/null +++ b/app/code/Magento/CustomerDownloadableGraphQl/registration.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_DownloadableGraphQl', __DIR__); diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index 788a5fc601ee1..8e877ffe8360a 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -1,22 +1,6 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. -type Query { - customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false) -} - -type CustomerDownloadableProducts { - items: [CustomerDownloadableProduct] @doc(description: "List of purchased downloadable items") -} - -type CustomerDownloadableProduct { - order_increment_id: String - date: String - status: String - download_url: String - remaining_downloads: String -} - type DownloadableProduct implements ProductInterface, CustomizableProductInterface @doc(description: "DownloadableProduct defines a product that the customer downloads") { downloadable_product_samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about samples of this downloadable product.") downloadable_product_links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about the links for this downloadable product") From 9b4b44f0fe094da2ccf20a90a29f561fab50a107 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Wed, 26 Jun 2019 16:11:53 +0300 Subject: [PATCH 080/841] MAGETWO-44170: Not pass functional test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Add a tag about migration tests. --- .../Test/AdminProductTypeSwitchingOnEditingTest.xml | 4 ++-- .../Product/ProductTypeSwitchingOnUpdateTest.php | 1 - .../Product/ProductTypeSwitchingOnUpdateTest.xml | 12 ++++++------ 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 116fbc1b6b455..2154a89a5fa73 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -94,7 +94,7 @@ <comment userInput="Delete product configuration" stepKey="commentDeleteConfigs"/> <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToConfigProductPage"/> <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> - <conditionalClick selector="{{ AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection" /> + <conditionalClick selector="{{ AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption1Actions"/> <click selector="{{AdminProductFormConfigurationsSection.removeProductBtn}}" stepKey="clickRemoveOption1"/> <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption2Actions"/> @@ -221,7 +221,7 @@ <actionGroup ref="logout" stepKey="logout"/> </after> <!--Change product type to Downloadable--> - <comment userInput="-Change product type to Downloadable" stepKey="commentCreateDownloadable"/> + <comment userInput="Change product type to Downloadable" stepKey="commentCreateDownloadable"/> <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToDownloadableProductPage"/> <waitForPageLoad stepKey="waitForDownloadableProductPageLoad"/> <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php index 2abd17fce5b45..90cd6bdb76328 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php @@ -36,7 +36,6 @@ class ProductTypeSwitchingOnUpdateTest extends Injectable { /* tags */ - const TEST_TYPE = 'acceptance_test'; const MVP = 'yes'; /* end tags */ diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml index 3d3e36754e92c..732dac98e0779 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml @@ -11,7 +11,7 @@ <data name="productOrigin" xsi:type="string">catalogProductSimple::default</data> <data name="product" xsi:type="string">configurableProduct::default</data> <data name="actionName" xsi:type="string">-</data> - <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductsInGrid" /> @@ -35,7 +35,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation4"> - <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productOrigin" xsi:type="string">configurableProduct::default</data> <data name="product" xsi:type="string">catalogProductVirtual::required_fields</data> <data name="actionName" xsi:type="string">deleteVariations</data> @@ -50,7 +50,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation6"> - <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productOrigin" xsi:type="string">catalogProductVirtual::default</data> <data name="product" xsi:type="string">configurableProduct::not_virtual_for_type_switching</data> <data name="actionName" xsi:type="string">-</data> @@ -63,7 +63,7 @@ <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductIsNotDisplayedSeparately" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation7"> - <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productOrigin" xsi:type="string">catalogProductVirtual::default</data> <data name="product" xsi:type="string">downloadableProduct::default</data> <data name="actionName" xsi:type="string">-</data> @@ -75,7 +75,7 @@ <constraint name="Magento\Downloadable\Test\Constraint\AssertDownloadableLinksData" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation8"> - <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productOrigin" xsi:type="string">downloadableProduct::default</data> <data name="product" xsi:type="string">catalogProductSimple::default</data> <data name="actionName" xsi:type="string">-</data> @@ -103,7 +103,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation11"> - <data name="tag" xsi:type="string">test_type:acceptance_test</data> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="productOrigin" xsi:type="string">catalogProductSimple::default</data> <data name="product" xsi:type="string">downloadableProduct::default</data> <data name="actionName" xsi:type="string">-</data> From bee0050f1bb5a16f72676a127b586a2740430df7 Mon Sep 17 00:00:00 2001 From: Dzmitry Tabusheu <dzmitry_tabusheu@epam.com> Date: Wed, 26 Jun 2019 18:59:05 +0300 Subject: [PATCH 081/841] MAGETWO-45666: "Refresh cache" message is not displayed when changing category page layout - Added observer for invalidating cache on category design change --- app/code/Magento/Catalog/Model/Category.php | 15 ++++++++++----- .../InvalidateCacheOnCategoryDesignChange.php | 15 ++------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index d911bec0aaac9..7fe5d5ba3ab4e 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -128,6 +128,7 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements 'page_layout', 'custom_layout_update', 'custom_apply_to_products', + 'custom_use_parent_settings', ]; /** @@ -317,9 +318,11 @@ protected function getCustomAttributesCodes() * @throws \Magento\Framework\Exception\LocalizedException * @return \Magento\Catalog\Model\ResourceModel\Category * @deprecated because resource models should be used directly + * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod */ protected function _getResource() { + //phpcs:enable Generic.CodeAnalysis.UselessOverridingMethod return parent::_getResource(); } @@ -606,11 +609,13 @@ public function getUrl() return $this->getData('url'); } - $rewrite = $this->urlFinder->findOneByData([ - UrlRewrite::ENTITY_ID => $this->getId(), - UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, - UrlRewrite::STORE_ID => $this->getStoreId(), - ]); + $rewrite = $this->urlFinder->findOneByData( + [ + UrlRewrite::ENTITY_ID => $this->getId(), + UrlRewrite::ENTITY_TYPE => CategoryUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::STORE_ID => $this->getStoreId(), + ] + ); if ($rewrite) { $this->setData('url', $this->getUrlInstance()->getDirectUrl($rewrite->getRequestPath())); Profiler::stop('REWRITE: ' . __METHOD__); diff --git a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php index 6bad77b26ad0c..1e57bb50eaf4d 100644 --- a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php +++ b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php @@ -15,17 +15,6 @@ */ class InvalidateCacheOnCategoryDesignChange implements ObserverInterface { - /** - * @var array - */ - private $designAttributes = [ - 'custom_design', - 'page_layout', - 'custom_layout_update', - 'custom_apply_to_products', - 'custom_use_parent_settings' - ]; - /** * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList */ @@ -43,8 +32,8 @@ public function execute(Observer $observer) { $category = $observer->getEvent()->getEntity(); if (!$category->isObjectNew()) { - foreach ($this->designAttributes as $designAttribute) { - if ($category->dataHasChangedFor($designAttribute)) { + foreach ($category->getDesignAttributes() as $designAttribute) { + if ($category->dataHasChangedFor($designAttribute->getAttributeCode())) { $this->cacheTypeList->invalidate( [ \Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER, From af956595235f7739eba3119c447b1ccce711aece Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Thu, 27 Jun 2019 10:54:21 +0300 Subject: [PATCH 082/841] MAGETWO-44170: Not pass functional test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Fix a typo in the test. --- .../Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 2154a89a5fa73..22eaa9c8b34bb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -385,7 +385,7 @@ <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection"/> - <unCheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> + <uncheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForProduct"/> <actionGroup ref="saveProductForm" stepKey="saveDownloadssdableProductForm"/> <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> From 61138dd35e6993a7caa6c09f744392bbf73d4689 Mon Sep 17 00:00:00 2001 From: Aliaksei Yakimovich2 <aliaksei_yakimovich2@epam.com> Date: Wed, 26 Jun 2019 23:25:52 +0300 Subject: [PATCH 083/841] MAGETWO-70885: [SOAP] The 'incrementId' property of the order with state 'complete' is increased after order status update - Modified implementation without additional model loading; - Fixed tests; --- .../Magento/Sales/Model/OrderRepository.php | 11 ---- .../Model/ResourceModel/EntityAbstract.php | 5 +- .../Test/Unit/Model/OrderRepositoryTest.php | 1 - .../Unit/Model/ResourceModel/OrderTest.php | 2 +- .../Sales/Service/V1/OrderUpdateTest.php | 53 +++++++++++++++++-- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index 229ea42cadc81..bb523f0f91b36 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -266,17 +266,6 @@ public function save(\Magento\Sales\Api\Data\OrderInterface $entity) } } - $entityId = $entity->getEntityId(); - if ($entityId && $entity->getIncrementId() == null) { - try { - $loadedEntity = $this->get($entityId); - $entity->setIncrementId($loadedEntity->getIncrementId()); - // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock - } catch (NoSuchEntityException $e) { - // non-existent entity - } - } - $this->metadata->getMapper()->save($entity); $this->registry[$entity->getEntityId()] = $entity; return $this->registry[$entity->getEntityId()]; diff --git a/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php b/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php index 80612277e68d5..6c51546360496 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php +++ b/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php @@ -14,6 +14,7 @@ /** * Abstract sales entity provides to its children knowledge about eventPrefix and eventObject * + * phpcs:disable Magento2.Classes.AbstractApi * @api * @SuppressWarnings(PHPMD.NumberOfChildren) * @since 100.0.2 @@ -96,6 +97,7 @@ public function saveAttribute(\Magento\Framework\Model\AbstractModel $object, $a /** * Prepares data for saving and removes update time (if exists). + * * This prevents saving same update time on each entity update. * * @param \Magento\Framework\Model\AbstractModel $object @@ -114,6 +116,7 @@ protected function _prepareDataForSave(\Magento\Framework\Model\AbstractModel $o /** * Perform actions before object save + * * Perform actions before object save, calculate next sequence value for increment Id * * @param \Magento\Framework\Model\AbstractModel|\Magento\Framework\DataObject $object @@ -122,7 +125,7 @@ protected function _prepareDataForSave(\Magento\Framework\Model\AbstractModel $o protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) { /** @var \Magento\Sales\Model\AbstractModel $object */ - if ($object instanceof EntityInterface && $object->getIncrementId() == null) { + if ($object instanceof EntityInterface && $object->getEntityId() == null) { $store = $object->getStore(); $storeId = $store->getId(); if ($storeId === null) { diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php index b9c3cd1771ca1..7f0c0639d21f5 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php @@ -176,7 +176,6 @@ public function testSave() $shippingMock->expects($this->once())->method('getAddress'); $shippingMock->expects($this->once())->method('getMethod'); $this->metadata->expects($this->once())->method('getMapper')->willReturn($mapperMock); - $orderEntity->expects($this->once())->method('getIncrementId')->willReturn('0000000001'); $mapperMock->expects($this->once())->method('save'); $orderEntity->expects($this->any())->method('getEntityId')->willReturn(1); $this->orderRepository->save($orderEntity); diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php index 00cd7ebc7b070..2b281b71fb0ff 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php @@ -198,7 +198,7 @@ public function testSave() ->with('10000001') ->willReturnSelf(); $this->orderMock->expects($this->once()) - ->method('getIncrementId') + ->method('getEntityId') ->willReturn(null); $this->orderMock->expects($this->once()) ->method('getData') diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php index 212f1f2c12af7..df47ae3220467 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php @@ -45,11 +45,8 @@ public function testOrderUpdate() $order = $this->objectManager->get(\Magento\Sales\Model\Order::class) ->loadByIncrementId(self::ORDER_INCREMENT_ID); - $entityData = [ - OrderInterface::ENTITY_ID => $order->getId(), - OrderInterface::STATE => 'processing', - OrderInterface::STATUS => 'processing' - ]; + $entityData = $this->getOrderData($order); + $requestData = ['entity' => $entityData]; $serviceInfo = [ @@ -73,4 +70,50 @@ public function testOrderUpdate() $actualOrder->getData(OrderInterface::INCREMENT_ID) ); } + + /** + * Prepare order data for request + * + * @param \Magento\Sales\Model\Order $order + * @return array + */ + private function getOrderData(\Magento\Sales\Model\Order $order) + { + if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { + $entityData = $order->getData(); + unset($entityData[OrderInterface::INCREMENT_ID]); + $entityData[OrderInterface::STATE] = 'processing'; + $entityData[OrderInterface::STATUS] = 'processing'; + + $orderData = $order->getData(); + $orderData['billing_address'] = $order->getBillingAddress()->getData(); + $orderData['billing_address']['street'] = ['Street']; + + $orderItems = []; + foreach ($order->getItems() as $item) { + $orderItems[] = $item->getData(); + } + $orderData['items'] = $orderItems; + + $shippingAddress = $order->getShippingAddress()->getData(); + $orderData['extension_attributes']['shipping_assignments'] = + [ + [ + 'shipping' => [ + 'address' => $shippingAddress, + 'method' => 'flatrate_flatrate' + ], + 'items' => $order->getItems(), + 'stock_id' => null, + ] + ]; + } else { + $orderData = [ + OrderInterface::ENTITY_ID => $order->getId(), + OrderInterface::STATE => 'processing', + OrderInterface::STATUS => 'processing' + ]; + } + return $orderData; + } } From fc667066de5560a5ceccaf3f52f63c96e0009e00 Mon Sep 17 00:00:00 2001 From: natalia <natalia_marozava@epam.com> Date: Tue, 2 Jul 2019 11:28:54 +0300 Subject: [PATCH 084/841] MC-17251: Creating a preference for category product indexer breaks setup:di:compile - Changed return object --- .../Catalog/Model/Indexer/Category/Product/Action/Full.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 c5a9c78c42383..8117cf7e45ae5 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 @@ -150,9 +150,9 @@ private function switchTables(): void /** * Refresh entities index * - * @return Full + * @return AbstractAction */ - public function execute(): Full + public function execute(): AbstractAction { $this->createTables(); $this->clearReplicaTables(); From 3a565ed982b7169dec15af558d415660c67e0a45 Mon Sep 17 00:00:00 2001 From: Dzmitry Tabusheu <dzmitry_tabusheu@epam.com> Date: Thu, 4 Jul 2019 05:30:49 +0300 Subject: [PATCH 085/841] MAGETWO-45666: "Refresh cache" message is not displayed when changing category page layout - Fixed false design change detection when category created via API --- .../Magento/Catalog/Model/CategoryRepository.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Model/CategoryRepository.php b/app/code/Magento/Catalog/Model/CategoryRepository.php index 7485d9f6cb247..d5f6c58f9e6d1 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository.php @@ -13,6 +13,7 @@ use Magento\Catalog\Api\Data\CategoryInterface; /** + * Category repository * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CategoryRepository implements \Magento\Catalog\Api\CategoryRepositoryInterface @@ -70,7 +71,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) { @@ -106,6 +107,8 @@ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) $parentCategory = $this->get($parentId, $storeId); $existingData['path'] = $parentCategory->getPath(); $existingData['parent_id'] = $parentId; + $existingData['custom_apply_to_products'] = $existingData['custom_apply_to_products'] ?? 0; + $existingData['custom_use_parent_settings'] = $existingData['custom_use_parent_settings'] ?? 0; } $category->addData($existingData); try { @@ -125,7 +128,7 @@ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) } /** - * {@inheritdoc} + * @inheritdoc */ public function get($categoryId, $storeId = null) { @@ -146,7 +149,7 @@ public function get($categoryId, $storeId = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete(\Magento\Catalog\Api\Data\CategoryInterface $category) { @@ -167,7 +170,7 @@ public function delete(\Magento\Catalog\Api\Data\CategoryInterface $category) } /** - * {@inheritdoc} + * @inheritdoc */ public function deleteByIdentifier($categoryId) { @@ -208,6 +211,8 @@ protected function validateCategory(Category $category) } /** + * Get extensible data object converter + * * @return \Magento\Framework\Api\ExtensibleDataObjectConverter * * @deprecated 101.0.0 @@ -222,6 +227,8 @@ private function getExtensibleDataObjectConverter() } /** + * Get metadata pool + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() From 26816700ad5fd7e9330b8f3dce118d672f7567c6 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Thu, 4 Jul 2019 12:11:38 +0300 Subject: [PATCH 086/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix CR comments. --- .../ResourceModel/Product/Collection.php | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 7127aa2cb3e77..b4673412d25b2 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -2068,7 +2068,7 @@ protected function _applyProductLimitations() protected function _applyZeroStoreProductLimitations() { $filters = $this->_productLimitationFilters; - $categories = $this->getChildrenCategories((int)$filters['category_id'], []); + $categories = $this->getChildrenCategories((int)$filters['category_id']); $conditions = [ 'cat_pro.product_id=e.entity_id', @@ -2099,30 +2099,22 @@ protected function _applyZeroStoreProductLimitations() * Get children categories. * * @param int $categoryId - * @param array $categories * @return array */ - private function getChildrenCategories(int $categoryId, array $categories): array + private function getChildrenCategories(int $categoryId): array { - $categories[] = $categoryId; + $categoryIds[] = $categoryId; /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */ $categoryCollection = $this->categoryCollectionFactory->create(); - $category = $categoryCollection - ->addAttributeToSelect('is_anchor') + $categories = $categoryCollection ->addAttributeToFilter('is_anchor', 1) - ->addIdFilter([$categoryId]) - ->getFirstItem(); - if ($category) { + ->addAttributeToFilter('path', ['like' => $categoryId . '/%'])->getItems(); + foreach ($categories as $category) { $categoryChildren = $category->getChildren(); - $categoryChildrenIds = explode(',', $categoryChildren); - foreach ($categoryChildrenIds as $categoryChildrenId) { - if ($categoryChildrenId) { - $categories = $this->getChildrenCategories((int)$categoryChildrenId, $categories); - } - } + $categoryIds = array_merge($categoryIds, explode(',', $categoryChildren)); } - return $categories; + return $categoryIds; } /** From b08d80054c98d4260ba6d9e0fabcac3c04c0b403 Mon Sep 17 00:00:00 2001 From: natalia <natalia_marozava@epam.com> Date: Thu, 4 Jul 2019 14:32:25 +0300 Subject: [PATCH 087/841] MC-17251: Creating a preference for category product indexer breaks setup:di:compile - Added integration test --- .../Indexer/Category/Product/Action/Full.php | 21 ++++++++ .../Category/Product/Action/FullTest.php | 54 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Indexer/Category/Product/Action/Full.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php diff --git a/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Indexer/Category/Product/Action/Full.php b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Indexer/Category/Product/Action/Full.php new file mode 100644 index 0000000000000..120ad5840b16e --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Catalog/Model/Indexer/Category/Product/Action/Full.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\TestFramework\Catalog\Model\Indexer\Category\Product\Action; + +use Magento\Catalog\Model\Indexer\Category\Product\Action\Full as OriginFull; + +/** + * Class Full reindex action + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Full extends OriginFull +{ + // +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php new file mode 100644 index 0000000000000..59f312daca4d6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\Indexer\Category\Product\Action; + +use Magento\Catalog\Model\Indexer\Category\Product\Action\Full as OriginObject; +use Magento\TestFramework\Catalog\Model\Indexer\Category\Product\Action\Full as PreferenceObject; +use Magento\Framework\Interception\PluginListInterface; + +/** + * Class FullTest + * @package Magento\Catalog\Model\Indexer\Category\Product\Action + */ +class FullTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var PreferenceObject + */ + private $interceptor; + + /** + * List of plugins + * + * @var PluginListInterface + */ + private $pluginList; + + /** + * Prepare data for test + */ + protected function setUp() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager->configure(['preferences' => [OriginObject::class => PreferenceObject::class]]); + $this->interceptor = $objectManager->get(OriginObject::class); + $this->pluginList = $objectManager->get(PluginListInterface::class); + } + + /** + * Test possibility to add object preference + */ + public function testPreference() + { + $interceptorClassName = get_class($this->interceptor); + + // Check interceptor class name + $this->assertEquals($interceptorClassName, PreferenceObject::class . '\Interceptor'); + + //check that there are no fatal errors + $this->pluginList->getNext($interceptorClassName, 'execute'); + } +} From 2d8d7047ca764dec901585376618ee658a0107ba Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Thu, 4 Jul 2019 18:00:28 +0400 Subject: [PATCH 088/841] MC-15341: Default product numbers to display results in poor display on Desktop - Updated automated test script --- .../Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml index 384d8ddea4f17..de84d00cabab9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml @@ -20,6 +20,10 @@ <before> <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1 "/> <magentoCLI stepKey="setFlatCatalogProduct" command="config:set catalog/frontend/flat_catalog_product 1 "/> + <magentoCLI command="config:set catalog/frontend/grid_per_page_values 12,24,36" stepKey="setAllowedProductsPerPageValue"/> + <magentoCLI command="config:set catalog/frontend/grid_per_page 12" stepKey="setDefaultProductsPerPageValue"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> <createData entity="PaginationProduct" stepKey="simpleProduct1"/> <createData entity="PaginationProduct" stepKey="simpleProduct2"/> From 8a04fd67a409deda19dfdeb1ab00874cdfeae397 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Mon, 24 Jun 2019 10:47:20 +0400 Subject: [PATCH 089/841] MC-16375: Unable to pay with Braintree Paypal (Payment Action = Authorize and Capture) for Gift Card as Guest - Added automated test script --- .../StorefrontPayPalPaymentActionGroup.xml | 23 +++++++++++++++++++ .../PayPalExpressCheckoutConfigSection.xml | 7 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml new file mode 100644 index 0000000000000..331acc1de628a --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/StorefrontPayPalPaymentActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="LoginToPayPalPaymentAccount"> + <arguments> + <argument name="userName" type="string" defaultValue="{{Payer.buyerEmail}}"/> + <argument name="password" type="string" defaultValue="{{Payer.buyerPassword}"/> + </arguments> + <fillField selector="{{PayPalPaymentSection.email}}" userInput="{{userName}}" stepKey="fillEmail"/> + <click selector="{{PayPalPaymentSection.nextButton}}" stepKey="clickNext"/> + <waitForElementVisible selector="{{PayPalPaymentSection.password}}" stepKey="waitForPasswordField"/> + <fillField selector="{{PayPalPaymentSection.password}}" userInput="{{password}}" stepKey="fillPassword"/> + <click selector="{{PayPalPaymentSection.loginBtn}}" stepKey="clickLogin"/> + <waitForPageLoad stepKey="waitForLoginPageLoad"/> + <click selector="{{PayPalPaymentSection.continueButton}}" stepKey="clickContinue"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml index 85f94cd8691a5..8d1b594d44e61 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml @@ -45,6 +45,9 @@ <element name="color" type="text" selector=".paypal-button-color-{{color}}" parameterized="true"/> </section> <section name="CheckoutPaymentSection"> + <element name="email" type="input" selector="#checkout-customer-email"/> + <element name="payPalPaymentBraintree" type="radio" selector="#braintree_paypal"/> + <element name="payPalFrame" type="iframe" selector="//iframe[contains(@class, 'zoid-component-frame zoid-visible')]" timeout="5"/> <element name="PayPalPaymentRadio" type="radio" selector="input#paypal_express.radio" timeout="30"/> <element name="PayPalBtn" type="radio" selector=".paypal-button.paypal-button-number-0" timeout="30"/> </section> @@ -58,5 +61,7 @@ <element name="cartIcon" type="text" selector="#transactionCart"/> <element name="itemName" type="text" selector="//span[@title='{{productName}}']" parameterized="true"/> <element name="PayPalSubmitBtn" type="text" selector="//input[@type='submit']"/> + <element name="nextButton" type="button" selector="#btnNext"/> + <element name="continueButton" type="button" selector=".continueButton"/> </section> -</sections> \ No newline at end of file +</sections> From 6491b27f785b75a8e71cbd37b3e77886b5e12a5e Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Fri, 5 Jul 2019 12:24:54 +0300 Subject: [PATCH 090/841] MAGETWO-44170: Not pass functional test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Change id for test cases. --- .../Test/AdminProductTypeSwitchingOnEditingTest.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 22eaa9c8b34bb..b1af17fb25838 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -85,7 +85,7 @@ <features value="Catalog"/> <title value="Configurable product type switching on editing to virtual product"/> <description value="Configurable product type switching on editing to virtual product"/> - <testCaseId value="MAGETWO-29633"/> + <testCaseId value="MC-17952"/> <useCaseId value="MAGETWO-44170"/> <severity value="MAJOR"/> <group value="catalog"/> @@ -129,7 +129,7 @@ <features value="Catalog"/> <title value="Virtual product type switching on editing to configurable product"/> <description value="Virtual product type switching on editing to configurable product"/> - <testCaseId value="MAGETWO-29633"/> + <testCaseId value="MC-17953"/> <useCaseId value="MAGETWO-44170"/> <severity value="MAJOR"/> <group value="catalog"/> @@ -202,7 +202,7 @@ <features value="Catalog"/> <title value="Virtual product type switching on editing to Downloadable product"/> <description value="Virtual product type switching on editing to Downloadable product"/> - <testCaseId value="MAGETWO-29633"/> + <testCaseId value="MC-17954"/> <useCaseId value="MAGETWO-44170"/> <severity value="MAJOR"/> <group value="catalog"/> @@ -258,7 +258,7 @@ <features value="Catalog"/> <title value="Downloadable product type switching on editing to Simple product"/> <description value="Downloadable product type switching on editing to Simple product"/> - <testCaseId value="MAGETWO-29633"/> + <testCaseId value="MC-17955"/> <useCaseId value="MAGETWO-44170"/> <severity value="MAJOR"/> <group value="catalog"/> @@ -297,7 +297,7 @@ <features value="Catalog"/> <title value="Simple product type switching on editing to downloadable product"/> <description value="Simple product type switching on editing to downloadable product"/> - <testCaseId value="MAGETWO-29633"/> + <testCaseId value="MC-17956"/> <useCaseId value="MAGETWO-44170"/> <severity value="MAJOR"/> <group value="catalog"/> @@ -353,7 +353,7 @@ <features value="Catalog"/> <title value="Downloadable product type switching on editing to configurable product"/> <description value="Downloadable product type switching on editing to configurable product"/> - <testCaseId value="MAGETWO-29633"/> + <testCaseId value="MC-17957"/> <useCaseId value="MAGETWO-44170"/> <severity value="MAJOR"/> <group value="catalog"/> From b78d67cce31dc7a308ba9067588f4e82ab5565a8 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Fri, 5 Jul 2019 13:35:51 +0300 Subject: [PATCH 091/841] MC-16375: Unable to pay with Braintree Paypal (Payment Action = Authorize and Capture) for Gift Card as Guest - Fix static --- .../view/frontend/web/js/view/payment/method-renderer/paypal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index 620e2fcd00f58..ae9f69c405c2b 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -197,7 +197,7 @@ define([ telephone: customer.phone, regionCode: address.state }; - + billingAddress = createBillingAddress(billingAddress); quote.billingAddress(billingAddress); }, From b31f618d87e3b7d4b70dc91bfe6248a4534ee26e Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Fri, 5 Jul 2019 12:55:53 +0000 Subject: [PATCH 092/841] Added createCustomerGraphQl plugin --- .../Plugin/Customer/CreateCustomerAccount.php | 60 +++++++++++++++++++ .../CustomerGraphQl/etc/graphql/di.xml | 3 + 2 files changed, 63 insertions(+) create mode 100644 app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php diff --git a/app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php b/app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php new file mode 100644 index 0000000000000..d2aa003125771 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php @@ -0,0 +1,60 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\CustomerGraphQl\Model\Plugin\Customer; + +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Plugin to update is_subscribed param according to system config + */ +class CreateCustomerAccount +{ + /** + * Configuration path to newsletter active setting + */ + const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * CreateCustomerAccount constructor. + * + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + ScopeConfigInterface $scopeConfig + ) { + $this->scopeConfig = $scopeConfig; + } + + /** + * Before Executing method. + * + * @param \Magento\CustomerGraphQl\Model\Customer\CreateCustomerAccount $subject + * @param $data + * @return array + */ + public function beforeExecute ( + \Magento\CustomerGraphQl\Model\Customer\CreateCustomerAccount $subject, $data + ) { + if (!$this->scopeConfig->getValue( + self::XML_PATH_NEWSLETTER_ACTIVE, + ScopeInterface::SCOPE_STORE + ) + ) { + $data['is_subscribed'] = false; + } + + return [$data]; + } +} diff --git a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml index cb4ab0b7b27f4..cafe1820b9279 100644 --- a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml @@ -6,6 +6,9 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\CustomerGraphQl\Model\Customer\CreateCustomerAccount"> + <plugin name="createCustomerGraphQl" type="Magento\CustomerGraphQl\Model\Plugin\Customer\CreateCustomerAccount"/> + </type> <type name="Magento\CustomerGraphQl\Model\Customer\UpdateCustomerData"> <arguments> <argument name="restrictedKeys" xsi:type="array"> From d5390758c8c3f4e42a85e3b0720995267fcf8660 Mon Sep 17 00:00:00 2001 From: shankar <konar.shankar2013@gmail.com> Date: Sun, 7 Jul 2019 11:01:57 +0530 Subject: [PATCH 093/841] Disabled the ACL for Advanced Tab --- app/code/Magento/Backend/etc/adminhtml/system.xml | 2 +- app/code/Magento/Config/etc/acl.xml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 65744e56d94ac..7301f900b7e9a 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -23,7 +23,7 @@ <section id="advanced" translate="label" type="text" sortOrder="910" showInDefault="0" showInWebsite="0" showInStore="0"> <label>Advanced</label> <tab>advanced</tab> - <resource>Magento_Backend::advanced</resource> + <resource>Magento_Config::advanced</resource> <group id="modules_disable_output" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Disable Modules Output</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset\Modules\DisableOutput</frontend_model> diff --git a/app/code/Magento/Config/etc/acl.xml b/app/code/Magento/Config/etc/acl.xml index 3c6d3faa4242f..922cb5facb805 100644 --- a/app/code/Magento/Config/etc/acl.xml +++ b/app/code/Magento/Config/etc/acl.xml @@ -12,7 +12,11 @@ <resource id="Magento_Backend::stores"> <resource id="Magento_Backend::stores_settings"> <resource id="Magento_Config::config" title="Configuration" translate="title" sortOrder="20"> - <resource id="Magento_Config::advanced" title="Advanced Section" translate="title" sortOrder="90" /> + <!-- + @deprecated Magento does not support custom disabling/enabling modules output since 2.2.0 version. + Section 'Advanced Section' was disabled. This section will be removed from code in one release. + --> + <resource id="Magento_Config::advanced" title="Advanced Section" translate="title" sortOrder="90" disabled="true" /> <resource id="Magento_Config::config_admin" title="Advanced Admin Section" translate="title" sortOrder="100" /> <resource id="Magento_Config::config_design" title="Design Section" translate="title" sortOrder="40" /> <resource id="Magento_Config::config_general" title="General Section" translate="title" sortOrder="20" /> From 1259496618d876b24fe4f241dee872bc7c73d891 Mon Sep 17 00:00:00 2001 From: Maksym Novik <novik.kor@gmail.com> Date: Sun, 7 Jul 2019 18:31:10 +0300 Subject: [PATCH 094/841] GraphQl-220: Implement exception logging. Stopped using arguments with the same type --- app/etc/graphql/di.xml | 8 +-- .../Framework/GraphQl/Query/ErrorHandler.php | 14 +++--- .../GraphQl/Query/Resolver/LoggerFactory.php | 39 +++++++++++++++ ...terface.php => LoggerFactoryInterface.php} | 4 +- .../GraphQl/Query/Resolver/ResolveLogger.php | 49 ------------------- 5 files changed, 49 insertions(+), 65 deletions(-) create mode 100644 lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php rename lib/internal/Magento/Framework/GraphQl/Query/Resolver/{ResolveLoggerInterface.php => LoggerFactoryInterface.php} (82%) delete mode 100644 lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php diff --git a/app/etc/graphql/di.xml b/app/etc/graphql/di.xml index 57b5687c660cb..474266bb9eff0 100644 --- a/app/etc/graphql/di.xml +++ b/app/etc/graphql/di.xml @@ -7,12 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Framework\GraphQl\Query\ErrorHandlerInterface" type="Magento\Framework\GraphQl\Query\ErrorHandler"/> - <type name="Magento\Framework\GraphQl\Query\Resolver\ResolveLogger"> - <arguments> - <argument name="clientLogger" xsi:type="object">GraphQLClientLogger</argument> - <argument name="serverLogger" xsi:type="object">GraphQLServerLogger</argument> - </arguments> - </type> + <preference for="Magento\Framework\GraphQl\Query\Resolver\LoggerFactoryInterface" type="Magento\Framework\GraphQl\Query\Resolver\LoggerFactory"/> <virtualType name="GraphQLClientLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="handlers" xsi:type="array"> @@ -37,5 +32,4 @@ <argument name="fileName" xsi:type="const">Magento\Framework\GraphQl\Query\ErrorHandler::SERVER_LOG_FILE</argument> </arguments> </virtualType> - <preference for="Magento\Framework\GraphQl\Query\Resolver\ResolveLoggerInterface" type="Magento\Framework\GraphQl\Query\Resolver\ResolveLogger"/> </config> diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php index 921314a50beff..874f714284936 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php @@ -8,7 +8,7 @@ namespace Magento\Framework\GraphQl\Query; use GraphQL\Error\ClientAware; -use Magento\Framework\GraphQl\Query\Resolver\ResolveLoggerInterface; +use Magento\Framework\GraphQl\Query\Resolver\LoggerFactoryInterface; /** * @inheritDoc @@ -21,17 +21,17 @@ class ErrorHandler implements ErrorHandlerInterface const CLIENT_LOG_FILE = 'var/log/graphql/client/exception.log'; /** - * @var ResolveLoggerInterface + * @var LoggerFactoryInterface */ - private $resolveLogger; + private $loggerFactory; /** - * @param ResolveLoggerInterface $resolveLogger + * @param LoggerFactoryInterface $loggerFactory */ public function __construct( - ResolveLoggerInterface $resolveLogger + LoggerFactoryInterface $loggerFactory ) { - $this->resolveLogger = $resolveLogger; + $this->loggerFactory = $loggerFactory; } /** @@ -41,7 +41,7 @@ public function handle(array $errors, callable $formatter): array { return array_map( function (ClientAware $error) use ($formatter) { - $this->resolveLogger->execute($error)->error($error); + $this->loggerFactory->getLogger($error)->error($error); return $formatter($error); }, diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php new file mode 100644 index 0000000000000..2b35b53981100 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php @@ -0,0 +1,39 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Query\Resolver; + +use GraphQL\Error\ClientAware; +use Magento\Framework\ObjectManagerInterface; +use Psr\Log\LoggerInterface; + +/** + * @inheritDoc + */ +class LoggerFactory implements LoggerFactoryInterface +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * @inheritDoc + */ + public function getLogger(ClientAware $clientAware): LoggerInterface + { + return $clientAware->isClientSafe() ? + $this->objectManager->get('GraphQLClientLogger') : + $this->objectManager->get('GraphQLServerLogger'); + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLoggerInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactoryInterface.php similarity index 82% rename from lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLoggerInterface.php rename to lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactoryInterface.php index ade416a3093bd..778fb61db994d 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLoggerInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactoryInterface.php @@ -15,7 +15,7 @@ * * @api */ -interface ResolveLoggerInterface +interface LoggerFactoryInterface { /** * Get logger to use for certain ClientAware exception @@ -24,5 +24,5 @@ interface ResolveLoggerInterface * * @return LoggerInterface */ - public function execute(ClientAware $clientAware): LoggerInterface; + public function getLogger(ClientAware $clientAware): LoggerInterface; } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php deleted file mode 100644 index 98e4f85b76128..0000000000000 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/ResolveLogger.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQl\Query\Resolver; - -use GraphQL\Error\ClientAware; -use Psr\Log\LoggerInterface; - -/** - * @inheritDoc - */ -class ResolveLogger implements ResolveLoggerInterface -{ - /** - * @var LoggerInterface - */ - private $clientLogger; - - /** - * @var LoggerInterface - */ - private $serverLogger; - - /** - * @param LoggerInterface $clientLogger - * @param LoggerInterface $serverLogger - */ - public function __construct( - LoggerInterface $clientLogger, - LoggerInterface $serverLogger - ) { - $this->clientLogger = $clientLogger; - $this->serverLogger = $serverLogger; - } - - /** - * @inheritDoc - */ - public function execute(ClientAware $clientAware): LoggerInterface - { - return $clientAware->isClientSafe() ? - $this->clientLogger : - $this->serverLogger; - } -} From 723bd8d4f3386c6e0372ebac27a62b2c546cd0b4 Mon Sep 17 00:00:00 2001 From: Nikita Chubukov <nikita_chubukov@epam.com> Date: Mon, 8 Jul 2019 13:06:06 +0300 Subject: [PATCH 095/841] MC-15256: Exported customer without modification can not be imported - Integration test --- .../CustomerImportExport/Model/Import/CustomerTest.php | 5 +++++ .../Model/Import/_files/customers_to_import.csv | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) 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 05d9c5d3acb1e..54890f7db3a1d 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -133,6 +133,11 @@ public function testImportData() $updatedCustomer->getCreatedAt(), 'Creation date must be changed' ); + $this->assertNotEquals( + $existingCustomer->getGender(), + $updatedCustomer->getGender(), + 'Gender must be changed' + ); } /** diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv index 30a283ce0502f..96c14c67607aa 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv @@ -1,7 +1,7 @@ email,_website,_store,confirmation,created_at,created_in,default_billing,default_shipping,disable_auto_group_change,dob,firstname,gender,group_id,lastname,middlename,password_hash,prefix,rp_token,rp_token_created_at,store_id,suffix,taxvat,website_id,password -AnthonyANealy@magento.com,base,admin,,5/6/2012 15:53,Admin,1,1,0,5/6/2010,Anthony,Male,1,Nealy,A.,6a9c9bfb2ba88a6ad2a64e7402df44a763e0c48cd21d7af9e7e796cd4677ee28:RF,,,,0,,,1, +AnthonyANealy@magento.com,base,admin,,5/6/2012 15:53,Admin,1,1,0,5/6/2010,Anthony,Female,1,Nealy,A.,6a9c9bfb2ba88a6ad2a64e7402df44a763e0c48cd21d7af9e7e796cd4677ee28:RF,,,,0,,,1, LoriBBanks@magento.com,admin,admin,,5/6/2012 15:59,Admin,3,3,0,5/6/2010,Lori,Female,1,Banks,B.,7ad6dbdc83d3e9f598825dc58b84678c7351e4281f6bc2b277a32dcd88b9756b:pz,,,,0,,,0, CharlesTAlston@teleworm.us,base,admin,,5/6/2012 16:13,Admin,4,4,0,,Jhon,Female,1,Doe,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, -customer@example.com,base,admin,,5/6/2012 16:15,Admin,4,4,0,,Firstname,Male,1,Lastname,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, +customer@example.com,base,admin,,5/6/2012 16:15,Admin,4,4,0,,Firstname,Female,1,Lastname,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, julie.worrell@example.com,base,admin,,5/6/2012 16:19,Admin,4,4,0,,Julie,Female,1,Worrell,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, david.lamar@example.com,base,admin,,5/6/2012 16:25,Admin,4,4,0,,David,,1,Lamar,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, From 7b5ad356b0c4af0853d4a3731a4a92f50d33e8e8 Mon Sep 17 00:00:00 2001 From: Lilit Sargsyan <Lilit_Sargsyan@epam.com> Date: Mon, 8 Jul 2019 15:57:23 +0400 Subject: [PATCH 096/841] MC-17193: Company admin can make an order of someone else's order - Updated automated test script. --- .../Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index f7d24cda34142..e9a78d267b3fa 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -427,4 +427,7 @@ <click selector="{{OrdersGridSection.submitOrder}}" stepKey="submitOrder"/> <see stepKey="seeSuccessMessageForOrder" userInput="You created the order."/> </actionGroup> + <actionGroup name="createOrderFilteringCustomerByEmailActionGroup" extends="CreateOrderActionGroup"> + <click stepKey="chooseCustomer" selector="{{AdminOrdersGridSection.customerInOrdersSection(customer.email)}}"/> + </actionGroup> </actionGroups> From fc328d923d1e69d46501eef17a831954adcc01a5 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Mon, 8 Jul 2019 18:00:03 +0300 Subject: [PATCH 097/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix CR comments. --- .../Catalog/Model/ResourceModel/Product/Collection.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index b4673412d25b2..dffa49d97838c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -2108,8 +2108,10 @@ private function getChildrenCategories(int $categoryId): array /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */ $categoryCollection = $this->categoryCollectionFactory->create(); $categories = $categoryCollection - ->addAttributeToFilter('is_anchor', 1) - ->addAttributeToFilter('path', ['like' => $categoryId . '/%'])->getItems(); + ->addAttributeToFilter( + ['is_anchor', 'path'], + [1, ['like' => $categoryId . '/%']] + )->getItems(); foreach ($categories as $category) { $categoryChildren = $category->getChildren(); $categoryIds = array_merge($categoryIds, explode(',', $categoryChildren)); From 493f366dd2d8aeb9cabddb91016fe0eccd72586f Mon Sep 17 00:00:00 2001 From: shankar <konar.shankar2013@gmail.com> Date: Mon, 8 Jul 2019 21:38:27 +0530 Subject: [PATCH 098/841] Fixed resource in unit testing file of the Advanced tab --- .../_files/app/code/Magento/SomeModule/etc/adminhtml/system.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/adminhtml/system.xml b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/adminhtml/system.xml index 6d6c5954757c9..1733ba06c15d2 100644 --- a/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/adminhtml/system.xml +++ b/setup/src/Magento/Setup/Test/Unit/Module/Di/_files/app/code/Magento/SomeModule/etc/adminhtml/system.xml @@ -10,7 +10,7 @@ <section id="advanced" translate="label" type="text" sortOrder="910" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Advanced</label> <tab>advanced</tab> - <resource>Magento_Backend::advanced</resource> + <resource>Magento_Config::advanced</resource> <group id="modules_disable_output" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Disable Modules Output</label> <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset\Modules\DisableOutput\Proxy</frontend_model> From 1fc69414ae60e7b05ed2c1d243302cec3e3cb0e7 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Tue, 9 Jul 2019 14:07:14 +0400 Subject: [PATCH 099/841] MC-17007: "Set Active" action on customers grid does not work properly - Updated automated test script. --- .../Mftf/ActionGroup/AdminCustomerGridActionGroup.xml | 11 ----------- .../AdminEditCustomerInformationFromActionGroup.xml | 8 -------- .../Section/AdminCustomerGridMainActionsSection.xml | 1 - .../Test/Mftf/Section/AdminCustomerGridSection.xml | 4 ---- 4 files changed, 24 deletions(-) diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml index a247415664d30..86039056999b0 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml @@ -20,15 +20,4 @@ <click selector="{{AdminCustomerFiltersSection.apply}}" stepKey="applyFilter"/> <waitForPageLoad stepKey="waitForPageToLoad"/> </actionGroup> - <actionGroup name="adminSetCustomerActiveViaGrid"> - <arguments> - <argument name="customerEmail" type="string" defaultValue="{{Simple_US_CA_Customer.email}}"/> - </arguments> - <click selector="{{AdminCustomerGridMainActionsSection.customerCheckbox(customerEmail)}}" stepKey="chooseCustomer" /> - <click selector="{{AdminCustomerGridMainActionsSection.actions}}" stepKey="openActions"/> - <waitForElementVisible selector="{{AdminCustomerGridMainActionsSection.setActive}}" stepKey="waitForDropDownOpen"/> - <click selector="{{AdminCustomerGridMainActionsSection.setActive}}" stepKey="setActive"/> - <waitForPageLoad stepKey="waitForLoad"/> - <see userInput="A total of 1 record(s) were updated." stepKey="seeSuccessMessage"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml index f0bf7f4f1f4a7..ddeefeb3c3742 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml @@ -25,12 +25,4 @@ <waitForPageLoad stepKey="wait"/> <scrollToTopOfPage stepKey="scrollToTop"/> </actionGroup> - <actionGroup name="adminSetCustomerInactive"> - <click selector="{{AdminCustomerAccountInformationSection.accountInformationTab}}" stepKey="goToAccountInformation"/> - <waitForElementVisible selector="{{AdminCustomerAccountInformationSection.statusInactive}}" stepKey="waitForElement"/> - <click selector="{{AdminCustomerAccountInformationSection.statusInactive}}" stepKey="clickInactive"/> - <click selector="{{AdminCustomerMainActionsSection.saveAndContinue}}" stepKey="saveAndContinue"/> - <waitForPageLoad stepKey="waitForSaving"/> - <see userInput="You saved the customer." stepKey="seeSuccessMessage"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml index 9148e21f174ed..d644b581088bc 100755 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridMainActionsSection.xml @@ -15,6 +15,5 @@ <element name="actions" type="text" selector=".action-select"/> <element name="customerCheckbox" type="button" selector="//*[contains(text(),'{{arg}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']//input" parameterized="true"/> <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> - <element name="setActive" type="button" selector="//*[contains(@class, 'admin__data-grid-header')]//span[contains(@class,'action-menu-item') and text()='Set Active']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml index 88d8933f4f581..91363c614c1f8 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml @@ -17,8 +17,4 @@ <element name="customerEditLinkByEmail" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//a[@class='action-menu-item']" parameterized="true" timeout="30"/> <element name="customerGroupByEmail" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[text()='{{customerGroup}}']" parameterized="true"/> </section> - <section name="AdminCustomerGridInformationSection"> - <element name="status" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[contains(text(), '{{status}}')]" parameterized="true" timeout="30"/> - <element name="associatedCompany" type="text" selector="//tr[@class='data-row' and //div[text()='{{customerEmail}}']]//div[contains(text(), '{{companyName}}')]" parameterized="true" timeout="30/"/> - </section> </sections> From 9bbcd413f157d02d9052f8cd16382f86a2c1a527 Mon Sep 17 00:00:00 2001 From: Aliaksei Yakimovich2 <aliaksei_yakimovich2@epam.com> Date: Tue, 9 Jul 2019 13:43:06 +0300 Subject: [PATCH 100/841] MAGETWO-70885: [SOAP] The 'incrementId' property of the order with state 'complete' is increased after order status update - Fixed an issue with skipped incrent ids when order creates; --- app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php b/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php index 6c51546360496..812ada3decb91 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php +++ b/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php @@ -125,7 +125,7 @@ protected function _prepareDataForSave(\Magento\Framework\Model\AbstractModel $o protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) { /** @var \Magento\Sales\Model\AbstractModel $object */ - if ($object instanceof EntityInterface && $object->getEntityId() == null) { + if ($object instanceof EntityInterface && $object->getEntityId() == null && $object->getIncrementId() == null) { $store = $object->getStore(); $storeId = $store->getId(); if ($storeId === null) { From ff1b6586aaa567816b7a892936ccd6c8f8dea064 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Wed, 10 Jul 2019 11:37:36 +0300 Subject: [PATCH 101/841] MC-17193: Company admin can make an order of someone else's order - Fix actionGroup's name. --- .../Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index e9a78d267b3fa..e98023178cdfe 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -427,7 +427,7 @@ <click selector="{{OrdersGridSection.submitOrder}}" stepKey="submitOrder"/> <see stepKey="seeSuccessMessageForOrder" userInput="You created the order."/> </actionGroup> - <actionGroup name="createOrderFilteringCustomerByEmailActionGroup" extends="CreateOrderActionGroup"> + <actionGroup name="CreateOrderFilteringCustomerByEmailActionGroup" extends="CreateOrderActionGroup"> <click stepKey="chooseCustomer" selector="{{AdminOrdersGridSection.customerInOrdersSection(customer.email)}}"/> </actionGroup> </actionGroups> From 8892b0bf7275426f4aad603252d0e40ab39af675 Mon Sep 17 00:00:00 2001 From: Vasilii Burlacu <v.burlacu@atwix.com> Date: Wed, 10 Jul 2019 15:23:31 +0300 Subject: [PATCH 102/841] magento/magento:5023 - It is not possible to add MS tile image meta via default_head_blocks.xml # Introduced a new class that provides URL for MS tile image during page config metadata generation --- .../Magento/Framework/View/Page/Config.php | 30 ----------- .../Metadata/MsApplicationTileImage.php | 52 +++++++++++++++++++ .../Framework/View/Page/Config/Renderer.php | 16 ++++-- .../Test/Unit/Page/Config/RendererTest.php | 51 +++++++++++++++--- .../View/Test/Unit/Page/ConfigTest.php | 20 ------- 5 files changed, 110 insertions(+), 59 deletions(-) create mode 100644 lib/internal/Magento/Framework/View/Page/Config/Metadata/MsApplicationTileImage.php diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index a81bbc77dafda..44f4038860cda 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -137,11 +137,6 @@ class Config 'robots' => null, ]; - /** - * @var array - */ - private $additionalMetaAssets = ['msapplication-TileImage']; - /** * @var \Magento\Framework\App\State */ @@ -556,31 +551,6 @@ public function addBodyClass($className) return $this; } - /** - * Retrieve the additional meta assets - * - * @return array - */ - public function getAdditionalMetaAssets() - { - return $this->additionalMetaAssets; - } - - /** - * Adjust metadata content url - * - * @param string $content - * @return string $content - */ - public function getMetaAssetUrl($content) - { - if (!parse_url($content, PHP_URL_SCHEME)) { - return $this->assetRepo->getUrl($content); - } - - return $content; - } - /** * Set additional element attribute * diff --git a/lib/internal/Magento/Framework/View/Page/Config/Metadata/MsApplicationTileImage.php b/lib/internal/Magento/Framework/View/Page/Config/Metadata/MsApplicationTileImage.php new file mode 100644 index 0000000000000..ae6401334a251 --- /dev/null +++ b/lib/internal/Magento/Framework/View/Page/Config/Metadata/MsApplicationTileImage.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\View\Page\Config\Metadata; + +use Magento\Framework\View\Asset\Repository as AssetRepository; + +/** + * Class MsApplicationTileImage + * + * Returns the URL for page `msapplication-TileImage` meta + */ +class MsApplicationTileImage +{ + /**#@+ + * Constant of asset name + */ + const META_NAME = 'msapplication-TileImage'; + + /** + * @var AssetRepository + */ + private $assetRepo; + + /** + * @param AssetRepository $assetRepo + */ + public function __construct(AssetRepository $assetRepo) + { + $this->assetRepo = $assetRepo; + } + + /** + * Get asset URL from given metadata content + * + * @param string $content + * + * @return string + */ + public function getUrl(string $content): string + { + if (!parse_url($content, PHP_URL_SCHEME)) { + return $this->assetRepo->getUrl($content); + } + + return $content; + } +} diff --git a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php index adbac1b5a87a0..eae6126fa39c0 100644 --- a/lib/internal/Magento/Framework/View/Page/Config/Renderer.php +++ b/lib/internal/Magento/Framework/View/Page/Config/Renderer.php @@ -9,6 +9,7 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Asset\GroupedCollection; use Magento\Framework\View\Page\Config; +use Magento\Framework\View\Page\Config\Metadata\MsApplicationTileImage; /** * Page config Renderer model @@ -74,6 +75,11 @@ class Renderer implements RendererInterface */ protected $urlBuilder; + /** + * @var MsApplicationTileImage + */ + private $msApplicationTileImage; + /** * @param Config $pageConfig * @param \Magento\Framework\View\Asset\MergeService $assetMergeService @@ -81,6 +87,7 @@ class Renderer implements RendererInterface * @param \Magento\Framework\Escaper $escaper * @param \Magento\Framework\Stdlib\StringUtils $string * @param \Psr\Log\LoggerInterface $logger + * @param MsApplicationTileImage|null $msApplicationTileImage */ public function __construct( Config $pageConfig, @@ -88,7 +95,8 @@ public function __construct( \Magento\Framework\UrlInterface $urlBuilder, \Magento\Framework\Escaper $escaper, \Magento\Framework\Stdlib\StringUtils $string, - \Psr\Log\LoggerInterface $logger + \Psr\Log\LoggerInterface $logger, + MsApplicationTileImage $msApplicationTileImage = null ) { $this->pageConfig = $pageConfig; $this->assetMergeService = $assetMergeService; @@ -96,6 +104,8 @@ public function __construct( $this->escaper = $escaper; $this->string = $string; $this->logger = $logger; + $this->msApplicationTileImage = $msApplicationTileImage ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(MsApplicationTileImage::class); } /** @@ -179,8 +189,8 @@ protected function processMetadataContent($name, $content) if (method_exists($this->pageConfig, $method)) { $content = $this->pageConfig->$method(); } - if ($content && in_array($name, $this->pageConfig->getAdditionalMetaAssets())) { - $content = $this->pageConfig->getMetaAssetUrl($content); + if ($content && $name === $this->msApplicationTileImage::META_NAME) { + $content = $this->msApplicationTileImage->getUrl($content); } return $content; diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php index 06e8834e7854b..7767066e99992 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php @@ -8,6 +8,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Asset\GroupedCollection; +use Magento\Framework\View\Page\Config\Metadata\MsApplicationTileImage; use Magento\Framework\View\Page\Config\Renderer; use Magento\Framework\View\Page\Config\Generator; @@ -58,6 +59,11 @@ class RendererTest extends \PHPUnit\Framework\TestCase */ protected $loggerMock; + /** + * @var MsApplicationTileImage|\PHPUnit_Framework_MockObject_MockObject + */ + protected $msApplicationTileImageMock; + /** * @var \Magento\Framework\View\Asset\GroupedCollection|\PHPUnit_Framework_MockObject_MockObject */ @@ -99,6 +105,10 @@ protected function setUp() $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) ->getMock(); + $this->msApplicationTileImageMock = $this->getMockBuilder(MsApplicationTileImage::class) + ->disableOriginalConstructor() + ->getMock(); + $this->assetsCollection = $this->getMockBuilder(\Magento\Framework\View\Asset\GroupedCollection::class) ->setMethods(['getGroups']) ->disableOriginalConstructor() @@ -120,7 +130,8 @@ protected function setUp() 'urlBuilder' => $this->urlBuilderMock, 'escaper' => $this->escaperMock, 'string' => $this->stringMock, - 'logger' => $this->loggerMock + 'logger' => $this->loggerMock, + 'msApplicationTileImage' => $this->msApplicationTileImageMock ] ); } @@ -147,7 +158,8 @@ public function testRenderMetadata() 'content_type' => 'content_type_value', 'x_ua_compatible' => 'x_ua_compatible_value', 'media_type' => 'media_type_value', - 'og:video:secure_url' => 'secure_url' + 'og:video:secure_url' => 'secure_url', + 'msapplication-TileImage' => 'https://site.domain/ms-tile.jpg' ]; $metadataValueCharset = 'newCharsetValue'; @@ -155,7 +167,8 @@ public function testRenderMetadata() . '<meta name="metadataName" content="metadataValue"/>' . "\n" . '<meta http-equiv="Content-Type" content="content_type_value"/>' . "\n" . '<meta http-equiv="X-UA-Compatible" content="x_ua_compatible_value"/>' . "\n" - . '<meta property="og:video:secure_url" content="secure_url"/>' . "\n"; + . '<meta property="og:video:secure_url" content="secure_url"/>' . "\n" + . '<meta name="msapplication-TileImage" content="https://site.domain/ms-tile.jpg"/>' . "\n"; $this->stringMock->expects($this->at(0)) ->method('upperCaseWords') @@ -171,10 +184,36 @@ public function testRenderMetadata() ->method('getMetadata') ->will($this->returnValue($metadata)); + $this->msApplicationTileImageMock + ->expects($this->once()) + ->method('getUrl') + ->with('https://site.domain/ms-tile.jpg') + ->will($this->returnValue('https://site.domain/ms-tile.jpg')); + + $this->assertEquals($expected, $this->renderer->renderMetadata()); + } + + /** + * Test renderMetadata when it has 'msapplication-TileImage' meta passed + */ + public function testRenderMetadataWithMsApplicationTileImageAsset() + { + $metadata = [ + 'msapplication-TileImage' => 'images/ms-tile.jpg' + ]; + $expectedMetaUrl = 'https://site.domain/images/ms-tile.jpg'; + $expected = '<meta name="msapplication-TileImage" content="' . $expectedMetaUrl . '"/>' . "\n"; + $this->pageConfigMock - ->expects($this->any()) - ->method('getAdditionalMetaAssets') - ->will($this->returnValue(['msapplication-TileImage'])); + ->expects($this->once()) + ->method('getMetadata') + ->will($this->returnValue($metadata)); + + $this->msApplicationTileImageMock + ->expects($this->once()) + ->method('getUrl') + ->with('images/ms-tile.jpg') + ->will($this->returnValue($expectedMetaUrl)); $this->assertEquals($expected, $this->renderer->renderMetadata()); } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php index 56ac3cea637dc..a1d522032a188 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/ConfigTest.php @@ -377,26 +377,6 @@ public function testAddRss() $this->assertInstanceOf(\Magento\Framework\View\Page\Config::class, $this->model->addRss($title, $href)); } - /** - * Testing case for getting meta asset URL with URL in content - */ - public function testGetMetaAssetUrlWithUrlInContent() - { - $this->assetRepo->expects($this->never())->method('getUrl'); - $this->assertEquals('http://test.com/image.png', $this->model->getMetaAssetUrl('http://test.com/image.png')); - } - - /** - * Testing case for getting meta asset URL without URL in content - */ - public function testGetMetaAssetUrlWithoutUrlInContent() - { - $this->assetRepo->expects($this->once())->method('getUrl')->with('image.png')->will( - $this->returnValue('http://test.com/image.png') - ); - $this->assertEquals('http://test.com/image.png', $this->model->getMetaAssetUrl('image.png')); - } - public function testAddBodyClass() { $className = 'test class'; From 2fd6ef1c26c58f0a8b083c3a7cdd5b6aa16a0ba7 Mon Sep 17 00:00:00 2001 From: Vasilii Burlacu <v.burlacu@atwix.com> Date: Wed, 10 Jul 2019 16:18:56 +0300 Subject: [PATCH 103/841] magento/magento:20764 - Mass-action delete for Widgets # Fixed codestyle and added improvements requested by maintainer --- .../Adminhtml/Widget/Instance/MassDelete.php | 22 ++++++++++--------- ...tInstanceById.php => DeleteWidgetById.php} | 7 +++--- app/code/Magento/Widget/etc/di.xml | 2 -- 3 files changed, 16 insertions(+), 15 deletions(-) rename app/code/Magento/Widget/Model/{DeleteWidgetInstanceById.php => DeleteWidgetById.php} (92%) diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php index 2aa49e547360c..af9ceeb725c0a 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php @@ -4,6 +4,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Widget\Controller\Adminhtml\Widget\Instance; use Magento\Backend\App\Action; @@ -14,7 +16,7 @@ use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Widget\Model\DeleteWidgetInstanceById; +use Magento\Widget\Model\DeleteWidgetById; /** * Class MassDelete @@ -29,20 +31,20 @@ class MassDelete extends Action implements HttpPostActionInterface const ADMIN_RESOURCE = 'Magento_Widget::widget_instance'; /** - * @var DeleteWidgetInstanceById + * @var DeleteWidgetById */ - private $deleteWidgetInstanceById; + private $deleteWidgetById; /** * @param Context $context - * @param DeleteWidgetInstanceById $deleteWidgetInstanceById + * @param DeleteWidgetById $deleteWidgetById */ public function __construct( Context $context, - DeleteWidgetInstanceById $deleteWidgetInstanceById + DeleteWidgetById $deleteWidgetById ) { parent::__construct($context); - $this->deleteWidgetInstanceById = $deleteWidgetInstanceById; + $this->deleteWidgetById = $deleteWidgetById; } /** @@ -51,7 +53,7 @@ public function __construct( * @return Redirect * @throws \Exception */ - public function execute() + public function execute(): Redirect { $deletedInstances = 0; $notDeletedInstances = []; @@ -69,7 +71,7 @@ public function execute() foreach ($instanceIds as $instanceId) { try { - $this->deleteWidgetInstanceById->execute((int) $instanceId); + $this->deleteWidgetById->execute((int) $instanceId); $deletedInstances++; } catch (NoSuchEntityException $e) { $notDeletedInstances[] = $instanceId; @@ -96,7 +98,7 @@ public function execute() /** * @return array */ - private function getInstanceIds() + private function getInstanceIds(): array { $instanceIds = $this->getRequest()->getParam('delete'); @@ -110,7 +112,7 @@ private function getInstanceIds() /** * @return ResultInterface|RedirectInterface */ - private function getResultPage() + private function getResultPage(): ?ResultInterface { return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); } diff --git a/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php b/app/code/Magento/Widget/Model/DeleteWidgetById.php similarity index 92% rename from app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php rename to app/code/Magento/Widget/Model/DeleteWidgetById.php index a830dbfcbd436..bdebaa69fa531 100644 --- a/app/code/Magento/Widget/Model/DeleteWidgetInstanceById.php +++ b/app/code/Magento/Widget/Model/DeleteWidgetById.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Widget\Model; @@ -12,9 +13,9 @@ use Magento\Widget\Model\Widget\Instance as WidgetInstance; /** - * Class DeleteWidgetInstanceById + * Class DeleteWidgetById */ -class DeleteWidgetInstanceById +class DeleteWidgetById { /** * @var InstanceResourceModel @@ -57,7 +58,7 @@ public function execute(int $instanceId) * @return WidgetInstance * @throws NoSuchEntityException */ - private function getWidgetById(int $instanceId) + private function getWidgetById(int $instanceId): WidgetInstance { /** @var WidgetInstance $widgetInstance */ $widgetInstance = $this->instanceFactory->create(); diff --git a/app/code/Magento/Widget/etc/di.xml b/app/code/Magento/Widget/etc/di.xml index 73199b0a34096..2da5a351aa6ac 100644 --- a/app/code/Magento/Widget/etc/di.xml +++ b/app/code/Magento/Widget/etc/di.xml @@ -34,6 +34,4 @@ <plugin name="widget-layout-update-plugin" type="Magento\Widget\Model\ResourceModel\Layout\Plugin" sortOrder="10"/> </type> - <preference for="Magento\Widget\Api\DeleteWidgetInstanceByIdInterface" - type="Magento\Widget\Model\DeleteWidgetInstanceById" /> </config> From 76619e1ffff48918582de589a7203926c7af3949 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@magento.com> Date: Wed, 10 Jul 2019 09:36:23 -0500 Subject: [PATCH 104/841] Issue with gallery: media gallery loaded twice --- lib/web/mage/adminhtml/browser.js | 11 +++++++++++ .../adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js | 1 + 2 files changed, 12 insertions(+) diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js index 09ceafc011d6f..9019e941bc586 100644 --- a/lib/web/mage/adminhtml/browser.js +++ b/lib/web/mage/adminhtml/browser.js @@ -18,6 +18,7 @@ define([ ], function ($, wysiwyg, prompt, confirm, alert) { window.MediabrowserUtility = { windowId: 'modal_dialog_message', + modalLoaded: false, /** * @return {Number} @@ -51,6 +52,15 @@ define([ content = '<div class="popup-window" id="' + windowId + '"></div>', self = this; + if (this.modalLoaded === true) { + if (options && typeof options.closed !== 'undefined') { + this.modal.modal('option', 'closed', options.closed); + } + this.modal.modal('openModal'); + + return; + } + if (this.modal) { this.modal.html($(content).html()); @@ -74,6 +84,7 @@ define([ }).done(function (data) { self.modal.html(data).trigger('contentUpdated'); + self.modalLoaded = true; }); }, diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 9779be85133f8..b4f618842e763 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -117,6 +117,7 @@ define([ jQuery.when.apply(jQuery, deferreds).done(function () { tinyMCE4.init(settings); this.getPluginButtons().hide(); + varienGlobalEvents.clearEventHandlers('open_browser_callback'); this.eventBus.attachEventHandler('open_browser_callback', tinyMceEditors.get(self.id).openFileBrowser); }.bind(this)); }, From 1266735fc5ee37d52e915fd0bb76acc681bec89b Mon Sep 17 00:00:00 2001 From: vital_sery <vital_sery@epam.com> Date: Thu, 11 Jul 2019 16:29:28 +0300 Subject: [PATCH 105/841] MC-17012: Flaky MFTF Test: MC-14765: StorefrontVerifySearchSuggestionByProductDescriptionTest --- .../AdminCatalogSearchTermIndexSection.xml | 1 + .../AdminSearchTermActionGroup.xml | 8 +++ ...archSuggestionByProductDescriptionTest.xml | 53 ++++++++++--------- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml index aa0145b9f96cd..dcaf7fb3a561d 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml @@ -21,5 +21,6 @@ <element name="emptyRecords" type="text" selector="//tr[@class='data-grid-tr-no-data even']/td[@class='empty-text']"/> <element name="gridRow" type="text" selector="//tr[@data-role='row']"/> <element name="numberOfSearchTermResults" type="text" selector="//tr[@data-role='row']/td[@data-column='num_results']"/> + <element name="selectMassActionCheckbox" type="select" selector="//select[@id='search_term_grid_massaction-mass-select']"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml index e0b3d4b850bbb..fdd774edebba7 100644 --- a/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml +++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml @@ -27,4 +27,12 @@ <click selector="{{AdminCatalogSearchTermIndexSection.okButton}}" stepKey="clickOkButton"/> <waitForElementVisible selector="{{AdminCatalogSearchTermMessagesSection.successMessage}}" stepKey="waitForSuccessMessage"/> </actionGroup> + + <!-- Delete all existing search terms --> + <actionGroup name="DeleteAllSearchTerms"> + <selectOption userInput="selectAll" selector="{{AdminCatalogSearchTermIndexSection.selectMassActionCheckbox}}" stepKey="checkAllSearchTerms"/> + <selectOption selector="{{AdminCatalogSearchTermIndexSection.massActions}}" userInput="delete" stepKey="selectDeleteOption"/> + <click selector="{{AdminCatalogSearchTermIndexSection.submit}}" stepKey="clickSubmitButton"/> + <click selector="{{AdminCatalogSearchTermIndexSection.okButton}}" stepKey="clickOkButton"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml index 63a861b697a86..0ec33c48f259e 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml @@ -16,64 +16,69 @@ <severity value="CRITICAL"/> <testCaseId value="MC-14765"/> <group value="mtf_migrated"/> - <skip> - <issueId value="MC-17012"/> - </skip> </annotations> - <before> <!-- Login as admin --> + <comment userInput="Login as admin" stepKey="loginAsAdminComment"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - + <!-- Go to the catalog search term page --> + <comment userInput="Go to the catalog search term page" stepKey="goToSearchTermPageComment"/> + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <!-- Delete all search terms --> + <comment userInput="Delete all search terms" stepKey="deleteAllSearchTermsComment"/> + <actionGroup ref="DeleteAllSearchTerms" stepKey="deleteAllSearchTerms"/> <!-- Create product with description --> + <comment userInput="Create product with description" stepKey="createProductWithDescriptionComment"/> <createData entity="SimpleProductWithDescription" stepKey="simpleProduct"/> </before> <after> - <!--Delete create product --> - <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> - - <!--Go to the catalog search term page --> + <!-- Delete created product --> + <comment userInput="Delete created product" stepKey="deleteCreatedProductComment"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <!-- Go to the catalog search term page --> + <comment userInput="Go to the catalog search term page" stepKey="goToSearchTermPageComment2"/> <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> - - <!--Filter the search term --> + <!-- Filter the search term --> + <comment userInput="Filter search term" stepKey="filterSearchTermComment"/> <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByThirdSearchQuery"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> </actionGroup> - <!-- Delete created below search terms --> + <comment userInput="Delete created below search terms" stepKey="deleteCreatedBelowSearchTermsComment"/> <actionGroup ref="deleteSearchTerm" stepKey="deleteSearchTerms"/> <actionGroup ref="logout" stepKey="logout"/> </after> - <!-- Go to storefront home page --> + <comment userInput="Go to storefront home page" stepKey="goToStorefrontHomePageComment"/> <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> - - <!--Storefront quick search by product name --> + <!-- Storefront quick search by product name --> + <comment userInput="Storefront quick search by product name" stepKey="storefrontQuickSearchByProductNameComment"/> <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName"> <argument name="phrase" value="{{ApiProductDescription.value}}"/> </actionGroup> - - <!--Verify search suggestions and select the suggestion from dropdown options --> + <!-- Verify search suggestions and select the suggestion from dropdown options --> + <comment userInput="Verify search suggestions and select the suggestion from dropdown options" stepKey="verifySearchSuggestionsComment"/> <actionGroup ref="StoreFrontSelectDropDownSearchSuggestionActionGroup" stepKey="seeDropDownSearchSuggestion"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> </actionGroup> - <!-- Assert Product storefront main page --> + <comment userInput="See product name" stepKey="seeProductNameComment"/> <actionGroup ref="StorefrontAssertProductNameOnProductMainPageActionGroup" stepKey="seeProductName"> <argument name="productName" value="$$simpleProduct.name$$"/> </actionGroup> - - <!--Go to the catalog search term page --> + <!-- Go to the catalog search term page --> + <comment userInput="Go to the catalog search term page" stepKey="goToSearchTermPageComment3"/> <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> - - <!--Filter the search term --> + <!-- Filter the search term --> + <comment userInput="Filter search term" stepKey="filterSearchTermComment2"/> <actionGroup ref="searchTermFilterBySearchQuery" stepKey="filterByThirdSearchQuery"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> </actionGroup> - - <!--Assert Search Term in grid --> + <!-- Assert Search Term in grid --> + <comment userInput="Check is search term in grid or not" stepKey="isSearchTermInGridComment"/> <see stepKey="seeSearchTermInGrid" selector="{{AdminCatalogSearchTermIndexSection.gridRow}}" userInput="{{ApiProductDescription.value}}" /> <see selector="{{AdminCatalogSearchTermIndexSection.numberOfSearchTermResults}}" userInput="1" stepKey="seeNumberOfSearchTermResultInGrid"/> </test> From 51b433c519ad304225a8c952fbcc30c9c32a9720 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Thu, 11 Jul 2019 21:31:59 +0000 Subject: [PATCH 106/841] Moved logic from plugin to CreateCustomer --- .../Plugin/Customer/CreateCustomerAccount.php | 60 ------------------- .../Model/Resolver/CreateCustomer.php | 27 ++++++++- .../CustomerGraphQl/etc/graphql/di.xml | 3 - 3 files changed, 26 insertions(+), 64 deletions(-) delete mode 100644 app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php diff --git a/app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php b/app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php deleted file mode 100644 index d2aa003125771..0000000000000 --- a/app/code/Magento/CustomerGraphQl/Model/Plugin/Customer/CreateCustomerAccount.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types = 1); - -namespace Magento\CustomerGraphQl\Model\Plugin\Customer; - -use Magento\Store\Model\ScopeInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; - -/** - * Plugin to update is_subscribed param according to system config - */ -class CreateCustomerAccount -{ - /** - * Configuration path to newsletter active setting - */ - const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; - - /** - * @var ScopeConfigInterface - */ - private $scopeConfig; - - /** - * CreateCustomerAccount constructor. - * - * @param ScopeConfigInterface $scopeConfig - */ - public function __construct( - ScopeConfigInterface $scopeConfig - ) { - $this->scopeConfig = $scopeConfig; - } - - /** - * Before Executing method. - * - * @param \Magento\CustomerGraphQl\Model\Customer\CreateCustomerAccount $subject - * @param $data - * @return array - */ - public function beforeExecute ( - \Magento\CustomerGraphQl\Model\Customer\CreateCustomerAccount $subject, $data - ) { - if (!$this->scopeConfig->getValue( - self::XML_PATH_NEWSLETTER_ACTIVE, - ScopeInterface::SCOPE_STORE - ) - ) { - $data['is_subscribed'] = false; - } - - return [$data]; - } -} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index e12c636f0edf6..848f418e85917 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -13,12 +13,24 @@ use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; /** * Create customer account resolver */ class CreateCustomer implements ResolverInterface { + /** + * Configuration path to newsletter active setting + */ + const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @var ExtractCustomerData */ @@ -30,13 +42,18 @@ class CreateCustomer implements ResolverInterface private $createCustomerAccount; /** + * CreateCustomer constructor. + * * @param ExtractCustomerData $extractCustomerData * @param CreateCustomerAccount $createCustomerAccount + * @param ScopeConfigInterface $scopeConfig */ public function __construct( ExtractCustomerData $extractCustomerData, - CreateCustomerAccount $createCustomerAccount + CreateCustomerAccount $createCustomerAccount, + ScopeConfigInterface $scopeConfig ) { + $this->scopeConfig = $scopeConfig; $this->extractCustomerData = $extractCustomerData; $this->createCustomerAccount = $createCustomerAccount; } @@ -55,6 +72,14 @@ public function resolve( throw new GraphQlInputException(__('"input" value should be specified')); } + if (!$this->scopeConfig->getValue( + self::XML_PATH_NEWSLETTER_ACTIVE, + ScopeInterface::SCOPE_STORE + ) + ) { + $args['input']['is_subscribed'] = false; + } + $customer = $this->createCustomerAccount->execute($args['input']); $data = $this->extractCustomerData->execute($customer); diff --git a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml index cafe1820b9279..cb4ab0b7b27f4 100644 --- a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml @@ -6,9 +6,6 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="Magento\CustomerGraphQl\Model\Customer\CreateCustomerAccount"> - <plugin name="createCustomerGraphQl" type="Magento\CustomerGraphQl\Model\Plugin\Customer\CreateCustomerAccount"/> - </type> <type name="Magento\CustomerGraphQl\Model\Customer\UpdateCustomerData"> <arguments> <argument name="restrictedKeys" xsi:type="array"> From dfa329ff5d9b2ac7f39f23e81e7be7e6b76d882a Mon Sep 17 00:00:00 2001 From: sdovbenko <sdovbenko@geeksforless.net> Date: Fri, 12 Jul 2019 01:19:29 +0300 Subject: [PATCH 107/841] Added test method testCreateCustomerSubscribed --- .../GraphQl/Customer/CreateCustomerTest.php | 33 +++++++++++++++++++ .../Customer/_files/customer_subscribe.php | 16 +++++++++ 2 files changed, 49 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index fc51f57a83a76..28f4030787613 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -241,6 +241,39 @@ public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput() $this->graphQlMutation($query); } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer_subscribe.php + * @throws \Exception + */ + public function testCreateCustomerSubscribed() + { + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $newEmail = 'new_customer@example.com'; + + $query = <<<QUERY +mutation { + createCustomer( + input: { + firstname: "{$newFirstname}" + lastname: "{$newLastname}" + email: "{$newEmail}" + is_subscribed: true + } + ) { + customer { + email + is_subscribed + } + } +} +QUERY; + + $response = $this->graphQlMutation($query); + + $this->assertEquals(false, $response['createCustomer']['customer']['is_subscribed']); + } + public function tearDown() { $newEmail = 'new_customer@example.com'; diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php new file mode 100644 index 0000000000000..9585ad8b02740 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Customer\Model\CustomerRegistry; + +$resourceConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Config\Model\ResourceModel\Config::class); + +$resourceConfig->saveConfig( + 'newsletter/general/active', + false, + 'default', + 0 +); \ No newline at end of file From cec3ca0baab08929afc3593b31f5379ce30ff052 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Fri, 12 Jul 2019 12:32:40 +0400 Subject: [PATCH 108/841] MC-15341: Default product numbers to display results in poor display on Desktop - Updated automated test script --- .../AdminCatalogStorefrontConfigSection.xml | 16 ++++++++ .../AdminCheckPaginationInStorefrontTest.xml | 41 ++++++++++--------- 2 files changed, 38 insertions(+), 19 deletions(-) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogStorefrontConfigSection.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogStorefrontConfigSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogStorefrontConfigSection.xml new file mode 100644 index 0000000000000..d0200f1e0a5b0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogStorefrontConfigSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogStorefrontConfigSection"> + <element name="sectionHeader" type="button" selector="#catalog_frontend-head"/> + <element name="productsPerPageAllowedValues" type="input" selector="#catalog_frontend_grid_per_page_values"/> + <element name="productsPerPageDefaultValue" type="input" selector="#catalog_frontend_grid_per_page"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml index de84d00cabab9..1b72458747067 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml @@ -20,10 +20,6 @@ <before> <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1 "/> <magentoCLI stepKey="setFlatCatalogProduct" command="config:set catalog/frontend/flat_catalog_product 1 "/> - <magentoCLI command="config:set catalog/frontend/grid_per_page_values 12,24,36" stepKey="setAllowedProductsPerPageValue"/> - <magentoCLI command="config:set catalog/frontend/grid_per_page 12" stepKey="setDefaultProductsPerPageValue"/> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> <createData entity="PaginationProduct" stepKey="simpleProduct1"/> <createData entity="PaginationProduct" stepKey="simpleProduct2"/> @@ -93,16 +89,24 @@ <deleteData createDataKey="simpleProduct30" stepKey="deleteSimpleProduct30"/> <actionGroup ref="logout" stepKey="logout"/> </after> - + <!--Verify default number of products displayed in the grid view--> + <comment userInput="Verify default number of products displayed in the grid view" stepKey="commentVerifyDefaultValues"/> + <amOnPage url="{{CatalogConfigPage.url}}" stepKey="goToCatalogConfigPagePage"/> + <waitForPageLoad stepKey="waitForConfigPageLoad" /> + <conditionalClick selector="{{AdminCatalogStorefrontConfigSection.sectionHeader}}" dependentSelector="{{AdminCatalogStorefrontConfigSection.productsPerPageAllowedValues}}" visible="false" stepKey="openCatalogConfigStorefrontSection"/> + <waitForElementVisible selector="{{AdminCatalogStorefrontConfigSection.productsPerPageAllowedValues}}" stepKey="waitForSectionOpen"/> + <seeInField selector="{{AdminCatalogStorefrontConfigSection.productsPerPageAllowedValues}}" userInput="12,24,36" stepKey="seeDefaultValueAllowedNumberProductsPerPage"/> + <seeInField selector="{{AdminCatalogStorefrontConfigSection.productsPerPageDefaultValue}}" userInput="12" stepKey="seeDefaultValueProductPerPage"/> <!--Open Category Page and select created category--> + <comment userInput="Open Category Page and select created category" stepKey="commentOpenCategoryPage"/> <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> <waitForPageLoad stepKey="waitForPageToLoad1"/> <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> <waitForPageLoad stepKey="waitForPageToLoad0"/> <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCreatedCategory"/> <waitForPageLoad stepKey="waitForPageToLoaded2"/> - <!--Select Products--> + <comment userInput="Select Products" stepKey="commentSelectProducts"/> <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory"/> <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> <waitForPageLoad stepKey="waitForProductsToLoad"/> @@ -110,7 +114,6 @@ <waitForElementVisible selector="{{CatalogProductsSection.resetFilter}}" time="30" stepKey="waitForResetButtonToVisible"/> <click selector="{{CatalogProductsSection.resetFilter}}" stepKey="clickOnResetFilter"/> <waitForPageLoad stepKey="waitForPageToLoad3"/> - <selectOption selector="{{AdminProductGridFilterSection.productPerPage}}" userInput="30" stepKey="selectPagePerView"/> <wait stepKey="waitFroPageToLoad1" time="30"/> <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="pagi" stepKey="selectProduct1"/> @@ -122,35 +125,35 @@ <waitForPageLoad stepKey="waitForCategorySaved"/> <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> - <!--Open Category Store Front Page--> + <comment userInput="Open Category Store Front Page" stepKey="commentOpenCategoryOnStorefront"/> <amOnPage url="{{_defaultCategory.name}}.html" stepKey="goToStorefront"/> <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeCategoryOnNavigation"/> <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="selectCategory"/> <waitForPageLoad stepKey="waitForProductToLoad"/> - <!--Select 12 items per page and verify number of products displayed in each page --> + <comment userInput="Select 12 items per page and verify number of products displayed in each page" stepKey="comment12ItemsPerPage"/> <conditionalClick selector="{{StorefrontCategoryTopToolbarSection.gridMode}}" visible="true" dependentSelector="{{StorefrontCategoryTopToolbarSection.gridMode}}" stepKey="seeProductGridIsActive"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToBottomToolbarSection"/> <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="12" stepKey="selectPerPageOption"/> - <!--Verify number of products displayed in First Page --> + <comment userInput="Verify number of products displayed in First Page" stepKey="commentVerifyNumberOfProducts"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInFirstPage"/> - <!--Verify number of products displayed in Second Page --> + <comment userInput="Verify number of products displayed in Second Page" stepKey="commentVerifyNumberOfProductsSecondPage"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton"/> <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage"/> <waitForPageLoad stepKey="waitForPageToLoad4"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInSecondPage"/> - <!--Verify number of products displayed in third Page --> + <comment userInput="Verify number of products displayed in third Page" stepKey="commentVerifyNumberOfProductsThirdPage"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton1"/> <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage1"/> <waitForPageLoad stepKey="waitForPageToLoad2"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInThirdPage"/> - <!--Change Pages using Previous Page selector and verify number of products displayed in each page--> + <comment userInput="Change Pages using Previous Page selector and verify number of products displayed in each page" stepKey="commentVerifyProductsOnEachPage"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage"/> <click selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="clickOnPreviousPage1"/> <waitForPageLoad stepKey="waitForPageToLoad5"/> @@ -159,26 +162,26 @@ <click selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="clickOnPreviousPage2"/> <waitForPageLoad stepKey="waitForPageToLoad6"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInFirstPage1"/> - <!--Select Pages by using page Number and verify number of products displayed--> + <comment userInput="Select Pages by using page Number and verify number of products displayed" stepKey="commentSelectPagesAndVerify"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToPreviousPage2"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('2')}}" stepKey="clickOnPage2"/> <waitForPageLoad stepKey="waitForPageToLoad7"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsInSecondPage2"/> - <!--Select Third Page using page number--> + <comment userInput="Select Third Page using page number" stepKey="commentSelectThirdPage"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToPreviousPage3"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('3')}}" stepKey="clickOnThirdPage"/> <waitForPageLoad stepKey="waitForPageToLoad8"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInThirdPage2"/> - <!--Select First Page using page number--> + <comment userInput="Select First Page using page number" stepKey="commentSelectFirstPage"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage4"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage"/> <waitForPageLoad stepKey="waitForPageToLoad9"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="12" stepKey="seeNumberOfProductsFirstPage2"/> - <!--Select 24 items per page and verify number of products displayed in each page --> + <comment userInput="Select 24 items per page and verify number of products displayed in each page" stepKey="commentSelect24ItemsPerPage"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage"/> <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="24" stepKey="selectPerPageOption1"/> <waitForPageLoad stepKey="waitForPageToLoad10"/> @@ -187,13 +190,13 @@ <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage2"/> <waitForPageLoad stepKey="waitForPageToLoad11"/> <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="6" stepKey="seeNumberOfProductsInSecondPage3"/> - <!--Select First Page using page number--> + <comment userInput="Select First Page using page number" stepKey="commentSelectFirstPageSecondTime"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="scrollToPreviousPage5"/> <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage2"/> <waitForPageLoad stepKey="waitForPageToLoad13"/> - <!--Select 36 items per page and verify number of products displayed in each page --> + <comment userInput="Select 36 items per page and verify number of products displayed in each page" stepKey="commentSelect36ItemsPerPage"/> <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage4"/> <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="36" stepKey="selectPerPageOption2"/> <waitForPageLoad stepKey="waitForPageToLoad12"/> From 1b24f7223876559da317f83b5b776b4a9575a0b4 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Thu, 27 Jun 2019 09:55:48 +0400 Subject: [PATCH 109/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" - Added automated test script - Remove doublicated activate-rule --- .../OtherPayPalConfigurationActionGroup.xml | 10 +++- ...ayPalSolutionsEnabledAtTheSameTimeTest.xml | 60 +++++++++++++++++++ .../Paypal/etc/adminhtml/rules/payment_ca.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_es.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_fr.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_gb.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_hk.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_it.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_jp.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_nz.xml | 1 - .../Paypal/etc/adminhtml/rules/payment_us.xml | 1 - 11 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml index 08ca6c7834384..2d42d8eea554f 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml @@ -23,6 +23,14 @@ <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForPageLoad2"/> </actionGroup> + <actionGroup name="enablePayPalSolution" extends="EnablePayPalConfiguration"> + <remove keyForRemoval="waitForOtherPayPalPaymentsSection"/> + <remove keyForRemoval="clickOtherPayPalPaymentsSection"/> + <remove keyForRemoval="seeAlertMessage"/> + <remove keyForRemoval="acceptEnablePopUp"/> + <remove keyForRemoval="saveConfig"/> + <remove keyForRemoval="waitForPageLoad2"/> + </actionGroup> <actionGroup name="CheckEnableOptionPayPalConfiguration"> <arguments> <argument name="payPalConfigType"/> @@ -37,4 +45,4 @@ <seeOptionIsSelected selector="{{payPalConfigType.enableSolution(countryCode)}}" userInput="{{enabledOption}}" stepKey="seeSelectedOption"/> <click selector="{{payPalConfigType.configureBtn(countryCode)}}" stepKey="clickWPSExpressConfigureBtn2"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml new file mode 100644 index 0000000000000..350add2862ce7 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminOnePayPalSolutionsEnabledAtTheSameTimeTest"> + <annotations> + <features value="Paypal"/> + <title value="Only one PayPal solution enabled at the same time"/> + <description value="Verify that only one PayPal solution can be enabled"/> + <severity value="MAJOR"/> + <testCaseId value="MC-17776"/> + <useCaseId value=" MC-15140"/> + <group value="paypal"/> + <skip> + <issueId value="MQE-1578"/> + </skip> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Set PayPal Payments Standard Configs--> + <comment userInput="Set PayPal Payments Standard Configs" stepKey="commentsetConfigs"/> + // TODO: set PayPal credentials + <magentoCLI command="config:set paypal/wpp/sandbox_flag 1" stepKey="setSandBox"/> + <magentoCLI command="config:set paypal/wpp/use_proxy 0" stepKey="setUseProxy"/> + <magentoCLI command="config:set payment/wps_express/active 1" stepKey="enableWPSExpress"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="config:set payment/wps_express/active 0" stepKey="disableWPSExpress"/> + <magentoCLI command="config:set payment/paypal_express/active 0" stepKey="disableExpressCheckout"/> + </after> + <!--Try to enable express checkout Solution--> + <comment userInput="Try to enable express checkout Solution" stepKey="commentTryEnableExpressCheckout"/> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <conditionalClick selector="{{OtherPayPalPaymentsConfigSection.expandTab('us')}}" dependentSelector="{{OtherPayPalPaymentsConfigSection.expandedTab('us')}}" visible="false" stepKey="clickOtherPayPalPaymentsSection"/> + <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckofut"> + <argument name="payPalConfigType" value="WPSExpressConfigSection"/> + <argument name="countryCode" value="us"/> + </actionGroup> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckout"> + <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> + <argument name="countryCode" value="us"/> + </actionGroup> + <seeInPopup userInput="There is already another PayPal solution enabled. Enable this solution instead?" stepKey="seeAlertMessage"/> + <cancelPopup stepKey="cancelPopup"/> + <!--Check only the correct solution is enabled --> + <comment userInput="Check only the correct solution is enabled" stepKey="commentCheckOnlyTheCorrectSolutionIsEnabled"/> + <conditionalClick selector="{{PayPalExpressCheckoutConfigSection.configureBtn('us')}}" dependentSelector="{{PayPalExpressCheckoutConfigSection.enableSolution('us')}}" visible="false" stepKey="clickPayPalExpressCheckoutSection"/> + <seeOptionIsSelected selector="{{PayPalExpressCheckoutConfigSection.enableSolution('us')}}" userInput="No" stepKey="seeSelectedOption"/> + </test> +</tests> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml index fc7fba8a9d3b4..36da32c9c14b1 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml @@ -193,7 +193,6 @@ <argument name="wps_other">wps_other</argument> <argument name="payflow_link_ca">payflow_link_ca</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml index 5c921ae1b1f63..28555d3ee5599 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml @@ -85,7 +85,6 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml index 0a7bcc374fe65..fea756ba66f3a 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml @@ -85,7 +85,6 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml index 04c969b59885e..0ce5cba8fe1dd 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml @@ -85,7 +85,6 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_express">wps_express</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml index c1183b53a5eb7..8fdc15c15b233 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml @@ -85,7 +85,6 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml index feb9ff11f4cff..75615209ec986 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml @@ -85,7 +85,6 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml index 77bb13fac8cb9..af0dab810e128 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml @@ -85,7 +85,6 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml index 541c531ca18c5..320a66b1a9b36 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml @@ -85,7 +85,6 @@ <rule type="paypalExpressMarkDisable" event="deactivate-rule"> <argument name="wps_other">wps_other</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml index 47faec9b51ef8..6a51330e060f7 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml @@ -414,7 +414,6 @@ <argument name="paypal_payflowpro_with_express_checkout">paypal_payflowpro_with_express_checkout</argument> <argument name="payflow_link_us">payflow_link_us</argument> </rule> - <rule type="simpleMarkEnable" event="activate-rule"/> <rule type="simpleDisable" event="deactivate-rule"/> <rule type="inContextEnable" event="activate-rule"/> <rule type="inContextDisable" event="deactivate-rule"/> From 61fa2cc2d46df71241fdfd1461c2e85da8548c7c Mon Sep 17 00:00:00 2001 From: Tan Sezer <t.sezer@youwe.nl> Date: Fri, 12 Jul 2019 16:14:10 +0200 Subject: [PATCH 110/841] - Moved plugin files under Plugin folder - Changed modifier of methods and variables - Use Magento serialize class --- .../CleanConfigurationTmpImages.php | 35 ++++++++++++------- .../CleanConfigurationTmpImagesTest.php | 5 +-- 2 files changed, 25 insertions(+), 15 deletions(-) rename app/code/Magento/ConfigurableProduct/{Controller/Adminhtml/Product/Initialization/Helper/Plugin => Plugin/Product/Initialization}/CleanConfigurationTmpImages.php (79%) rename app/code/Magento/ConfigurableProduct/Test/Unit/{Controller/Adminhtml/Product/Initialization/Helper/Plugin => Plugin/Product/Initialization}/CleanConfigurationTmpImagesTest.php (96%) diff --git a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php similarity index 79% rename from app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php rename to app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php index d85049940d323..b477ec9913330 100644 --- a/app/code/Magento/ConfigurableProduct/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImages.php +++ b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php @@ -1,11 +1,12 @@ <?php +declare(strict_types=1); /** * Product initialization helper * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin; +namespace Magento\ConfigurableProduct\Plugin\Product\Initialization; use Magento\Framework\App\Filesystem\DirectoryList; @@ -13,28 +14,34 @@ * Class CleanConfigurationTmpImages * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPCS.Magento2.Files.LineLength.MaxExceeded) */ class CleanConfigurationTmpImages { /** * @var \Magento\MediaStorage\Helper\File\Storage\Database */ - protected $fileStorageDb; + private $fileStorageDb; /** * @var \Magento\Catalog\Model\Product\Media\Config */ - protected $mediaConfig; + private $mediaConfig; /** * @var \Magento\Framework\Filesystem\Directory\WriteInterface */ - protected $mediaDirectory; + private $mediaDirectory; /** * @var \Magento\Framework\App\RequestInterface */ - protected $request; + private $request; + + /** + * @var \Magento\Framework\Serialize\SerializerInterface + */ + private $serialize; /** * @param \Magento\Framework\App\RequestInterface $request @@ -47,11 +54,13 @@ public function __construct( \Magento\Framework\App\RequestInterface $request, \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb, \Magento\Catalog\Model\Product\Media\Config $mediaConfig, - \Magento\Framework\Filesystem $filesystem + \Magento\Framework\Filesystem $filesystem, + \Magento\Framework\Serialize\SerializerInterface $serialize ) { $this->request = $request; $this->fileStorageDb = $fileStorageDb; $this->mediaConfig = $mediaConfig; + $this->serialize = $serialize; $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); } @@ -70,12 +79,12 @@ public function afterInitialize( ) { $configurations = $this->getConfigurations(); - foreach ($configurations as $variationId => $simpleProductData) { + foreach ($configurations as $simpleProductData) { if (!isset($simpleProductData['media_gallery']['images'])) { continue; } - foreach ($simpleProductData['media_gallery']['images'] as $imageId => $image) { + foreach ($simpleProductData['media_gallery']['images'] as $image) { $file = $this->getFilenameFromTmp($image['file']); if ($this->fileStorageDb->checkDbUsage()) { $filename = $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaShortUrl($file)); @@ -96,9 +105,9 @@ public function afterInitialize( * @param string $file * @return string */ - protected function getFilenameFromTmp($file) + private function getFilenameFromTmp($file) { - return strrpos($file, '.tmp') == strlen($file) - 4 ? substr($file, 0, strlen($file) - 4) : $file; + return strrpos($file, '.tmp') === strlen($file) - 4 ? substr($file, 0, strlen($file) - 4) : $file; } /** @@ -106,12 +115,12 @@ protected function getFilenameFromTmp($file) * * @return array */ - protected function getConfigurations() + private function getConfigurations() { $result = []; $configurableMatrix = $this->request->getParam('configurable-matrix-serialized', "[]"); - if (isset($configurableMatrix) && $configurableMatrix != "") { - $configurableMatrix = json_decode($configurableMatrix, true); + if (isset($configurableMatrix) && $configurableMatrix !== "") { + $configurableMatrix = $this->serialize->unserialize($configurableMatrix); foreach ($configurableMatrix as $item) { if (empty($item['was_changed']) && empty($item['newProduct'])) { diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImagesTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php similarity index 96% rename from app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImagesTest.php rename to app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php index 36bea34ef8452..8026dffbe13fe 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/CleanConfigurationTmpImagesTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php @@ -3,12 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\ConfigurableProduct\Test\Unit\Controller\Adminhtml\Product\Initialization\Helper\Plugin; +namespace Magento\ConfigurableProduct\Test\Unit\Plugin\Product\Initialization; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper as ProductInitializationHelper; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Media\Config as MediaConfig; -use Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\CleanConfigurationTmpImages; +use Magento\ConfigurableProduct\Plugin\Product\Initialization\CleanConfigurationTmpImages; use Magento\Framework\App\RequestInterface; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\Write; @@ -18,6 +18,7 @@ /** * Class CleanConfigurationTmpImagesTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPCS.Magento2.Files.LineLength.MaxExceeded) * @package Magento\ConfigurableProduct\Test\Unit\Controller\Adminhtml\Product\Initialization\Helper\Plugin */ class CleanConfigurationTmpImagesTest extends \PHPUnit\Framework\TestCase From dc8c4d35aa0f55e6ef819a1fb7087372194a4353 Mon Sep 17 00:00:00 2001 From: Nikita Chubukov <nikita_chubukov@epam.com> Date: Thu, 11 Jul 2019 01:10:15 +0300 Subject: [PATCH 111/841] MC-15256: Exported customer without modification can not be imported - Fix integration test --- .../Magento/CustomerImportExport/Model/Import/Customer.php | 4 ++-- .../Magento/Customer/_files/import_export/customer.php | 2 +- .../CustomerImportExport/Model/Import/CustomerTest.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index 991d4f3558adc..33dc7e55228bf 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -600,14 +600,14 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber) $isFieldNotSetAndCustomerDoesNotExist = !isset($rowData[$attributeCode]) && !$this->_getCustomerId($email, $website); $isFieldSetAndTrimmedValueIsEmpty - = isset($rowData[$attributeCode]) && '' === trim($rowData[$attributeCode]); + = isset($rowData[$attributeCode]) && '' === trim((string)$rowData[$attributeCode]); if ($isFieldRequired && ($isFieldNotSetAndCustomerDoesNotExist || $isFieldSetAndTrimmedValueIsEmpty)) { $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode); continue; } - if (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) { + if (isset($rowData[$attributeCode]) && strlen((string)$rowData[$attributeCode])) { if ($attributeParams['type'] == 'select' && empty($rowData[$attributeCode])) { continue; } diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/import_export/customer.php b/dev/tests/integration/testsuite/Magento/Customer/_files/import_export/customer.php index 215dd2a709418..3a39e62af0ccb 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/import_export/customer.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/import_export/customer.php @@ -30,7 +30,7 @@ )->setLastname( 'Alston' )->setGender( - 2 + '2' ); $customer->isObjectNew(true); 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 54890f7db3a1d..daab687408749 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -133,7 +133,7 @@ public function testImportData() $updatedCustomer->getCreatedAt(), 'Creation date must be changed' ); - $this->assertNotEquals( + $this->assertEquals( $existingCustomer->getGender(), $updatedCustomer->getGender(), 'Gender must be changed' From 2ca787283282e1063de089d37c71bad28d5ba835 Mon Sep 17 00:00:00 2001 From: sdovbenko <sdovbenko@geeksforless.net> Date: Sat, 13 Jul 2019 01:27:10 +0300 Subject: [PATCH 112/841] Added rollback for newsletter/general/active --- .../_files/customer_subscribe_rollback.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php new file mode 100644 index 0000000000000..43210e4cda694 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Writer $configWriter */ +$configWriter = $objectManager->create(WriterInterface::class); +$configWriter->delete('newsletter/general/active'); From c37a7a41649fd68ca7f800082c5eff25fdec9cc0 Mon Sep 17 00:00:00 2001 From: sdovbenko <sdovbenko@geeksforless.net> Date: Sat, 13 Jul 2019 01:34:05 +0300 Subject: [PATCH 113/841] Added todo --- .../Magento/Customer/_files/customer_subscribe_rollback.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php index 43210e4cda694..d8773e97b5db7 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Framework\App\Config\Storage\WriterInterface; From 9e4e46e3491182a930458134d486555c1f7e7096 Mon Sep 17 00:00:00 2001 From: Tan Sezer <t.sezer@youwe.nl> Date: Mon, 15 Jul 2019 10:30:10 +0200 Subject: [PATCH 114/841] Fix plugin path in DI.xml Use correct Serialize class in constructor Define class in doc --- .../Product/Initialization/CleanConfigurationTmpImages.php | 7 +++++-- app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php index b477ec9913330..946ef761dd22e 100644 --- a/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php +++ b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php @@ -39,7 +39,7 @@ class CleanConfigurationTmpImages private $request; /** - * @var \Magento\Framework\Serialize\SerializerInterface + * @var \Magento\Framework\Serialize\Serializer\Serialize */ private $serialize; @@ -48,6 +48,8 @@ class CleanConfigurationTmpImages * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig * @param \Magento\Framework\Filesystem $filesystem + * @param \Magento\Framework\Serialize\Serializer\Serialize $serialize + * * @throws \Magento\Framework\Exception\FileSystemException */ public function __construct( @@ -55,7 +57,7 @@ public function __construct( \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb, \Magento\Catalog\Model\Product\Media\Config $mediaConfig, \Magento\Framework\Filesystem $filesystem, - \Magento\Framework\Serialize\SerializerInterface $serialize + \Magento\Framework\Serialize\Serializer\Serialize $serialize ) { $this->request = $request; $this->fileStorageDb = $fileStorageDb; @@ -71,6 +73,7 @@ public function __construct( * @param \Magento\Catalog\Model\Product $configurableProduct * * @return \Magento\Catalog\Model\Product + * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterInitialize( diff --git a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml index 59f9204472b54..de6765138fce6 100644 --- a/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/adminhtml/di.xml @@ -9,7 +9,7 @@ <type name="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper"> <plugin name="configurable" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\Configurable" sortOrder="50" /> <plugin name="updateConfigurations" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\UpdateConfigurations" sortOrder="60" /> - <plugin name="cleanConfigurationTmpImages" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Initialization\Helper\Plugin\CleanConfigurationTmpImages" sortOrder="999" /> + <plugin name="cleanConfigurationTmpImages" type="Magento\ConfigurableProduct\Plugin\Product\Initialization\CleanConfigurationTmpImages" sortOrder="999" /> </type> <type name="Magento\Catalog\Controller\Adminhtml\Product\Builder"> <plugin name="configurable" type="Magento\ConfigurableProduct\Controller\Adminhtml\Product\Builder\Plugin" sortOrder="50" /> From 1666ce89abb376adf3fbc445d1ed2e868c34ad5c Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 15 Jul 2019 14:59:22 +0530 Subject: [PATCH 115/841] tear price change with tier price --- .../Model/Import/AdvancedPricing.php | 6 +++--- .../Test/Unit/Model/Import/AdvancedPricingTest.php | 2 +- app/code/Magento/Catalog/Model/Product/Type/Price.php | 2 +- .../Product/Attribute/Backend/GroupPrice/AbstractTest.php | 4 ++-- .../Catalog/Test/Unit/Pricing/Price/BasePriceTest.php | 8 ++++---- .../Test/Unit/Model/Indexer/IndexBuilderTest.php | 2 +- .../Catalog/Api/ProductTierPriceManagementTest.php | 4 ++-- .../GroupedProduct/Pricing/Price/FinalPriceTest.php | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index 591e648547d61..3e597c194a72b 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -50,7 +50,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract const VALIDATOR_WEBSITE = 'validator_website'; - const VALIDATOR_TEAR_PRICE = 'validator_tear_price'; + const VALIDATOR_TIER_PRICE = 'validator_tier_price'; /** * Validation failure message template definitions. @@ -221,7 +221,7 @@ public function __construct( $this->_catalogProductEntity = $this->_resourceFactory->create()->getTable('catalog_product_entity'); $this->_oldSkus = $this->retrieveOldSkus(); $this->_validators[self::VALIDATOR_WEBSITE] = $websiteValidator; - $this->_validators[self::VALIDATOR_TEAR_PRICE] = $tierPriceValidator; + $this->_validators[self::VALIDATOR_TIER_PRICE] = $tierPriceValidator; $this->errorAggregator = $errorAggregator; foreach (array_merge($this->errorMessageTemplates, $this->_messageTemplates) as $errorCode => $message) { @@ -536,7 +536,7 @@ protected function getWebSiteId($websiteCode) */ protected function getCustomerGroupId($customerGroup) { - $customerGroups = $this->_getValidator(self::VALIDATOR_TEAR_PRICE)->getCustomerGroups(); + $customerGroups = $this->_getValidator(self::VALIDATOR_TIER_PRICE)->getCustomerGroups(); return $customerGroup == self::VALUE_ALL_GROUPS ? 0 : $customerGroups[$customerGroup]; } diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php index 2aa59c1cfb758..9fac1c968dfc9 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php @@ -54,7 +54,7 @@ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\A protected $websiteValidator; /** - * @var AdvancedPricing\Validator\TearPrice |\PHPUnit_Framework_MockObject_MockObject + * @var AdvancedPricing\Validator\TierPrice |\PHPUnit_Framework_MockObject_MockObject */ protected $tierPriceValidator; diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index b30624b79dd51..c2e261f04a090 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -202,7 +202,7 @@ public function getChildFinalPrice($product, $productQty, $childProduct, $childP } /** - * Gets the 'tear_price' array from the product + * Gets the 'tier_price' array from the product * * @param Product $product * @param string $key diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php index 3003c2f8085e4..b2e1401479301 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/GroupPrice/AbstractTest.php @@ -65,11 +65,11 @@ public function testGetAffectedFields() $attribute->expects($this->any())->method('getAttributeId')->will($this->returnValue($attributeId)); $attribute->expects($this->any())->method('isStatic')->will($this->returnValue(false)); $attribute->expects($this->any())->method('getBackendTable')->will($this->returnValue('table')); - $attribute->expects($this->any())->method('getName')->will($this->returnValue('tear_price')); + $attribute->expects($this->any())->method('getName')->will($this->returnValue('tier_price')); $this->_model->setAttribute($attribute); $object = new \Magento\Framework\DataObject(); - $object->setTearPrice([['price_id' => 10]]); + $object->setTierPrice([['price_id' => 10]]); $object->setId(555); $this->assertEquals( diff --git a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php index b823549391257..279c3c3ac8587 100644 --- a/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Pricing/Price/BasePriceTest.php @@ -39,7 +39,7 @@ class BasePriceTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Catalog\Pricing\Price\TierPrice|\PHPUnit_Framework_MockObject_MockObject */ - protected $tearPriceMock; + protected $tierPriceMock; /** * @var \Magento\Catalog\Pricing\Price\SpecialPrice|\PHPUnit_Framework_MockObject_MockObject @@ -60,7 +60,7 @@ protected function setUp() $this->saleableItemMock = $this->createMock(\Magento\Catalog\Model\Product::class); $this->priceInfoMock = $this->createMock(\Magento\Framework\Pricing\PriceInfo\Base::class); $this->regularPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\RegularPrice::class); - $this->tearPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\TierPrice::class); + $this->tierPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\TierPrice::class); $this->specialPriceMock = $this->createMock(\Magento\Catalog\Pricing\Price\SpecialPrice::class); $this->calculatorMock = $this->createMock(\Magento\Framework\Pricing\Adjustment\Calculator::class); @@ -69,7 +69,7 @@ protected function setUp() ->will($this->returnValue($this->priceInfoMock)); $this->prices = [ 'regular_price' => $this->regularPriceMock, - 'tear_price' => $this->tearPriceMock, + 'tier_price' => $this->tierPriceMock, 'special_price' => $this->specialPriceMock, ]; @@ -97,7 +97,7 @@ public function testGetValue($specialPriceValue, $expectedResult) $this->regularPriceMock->expects($this->exactly(3)) ->method('getValue') ->will($this->returnValue(100)); - $this->tearPriceMock->expects($this->exactly(2)) + $this->tierPriceMock->expects($this->exactly(2)) ->method('getValue') ->will($this->returnValue(99)); $this->specialPriceMock->expects($this->any()) diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php index 920dcb8e1ede5..78668366bccdc 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/IndexBuilderTest.php @@ -252,7 +252,7 @@ public function testUpdateCatalogRuleGroupWebsiteData() ); $resourceMock->expects($this->any()) ->method('getMainTable') - ->will($this->returnValue('catalog_product_entity_tear_price')); + ->will($this->returnValue('catalog_product_entity_tier_price')); $backendModelMock->expects($this->any()) ->method('getResource') ->will($this->returnValue($resourceMock)); diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php index d5c035733942e..098a1fde766c5 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php @@ -34,12 +34,12 @@ public function testGetList($customerGroupId, $count, $value, $qty) ], ]; - $tearPriceList = $this->_webApiCall( + $tierPriceList = $this->_webApiCall( $serviceInfo, ['sku' => $productSku, 'customerGroupId' => $customerGroupId] ); - $this->assertCount($count, $tearPriceList); + $this->assertCount($count, $tierPriceList); if ($count) { $this->assertEquals($value, $tearPriceList[0]['value']); $this->assertEquals($qty, $tearPriceList[0]['qty']); diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php index 8b9fb9f0969ae..c2c63d3f13892 100644 --- a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php @@ -29,7 +29,7 @@ public function testFinalPrice() * @magentoDataFixture Magento/GroupedProduct/_files/product_grouped.php * @magentoAppIsolation enabled */ - public function testFinalPriceWithTearPrice() + public function testFinalPriceWithTierPrice() { $productRepository = Bootstrap::getObjectManager() ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); From 5ac00d12a04a07645d61836ec4a8e7a765c34b61 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 15 Jul 2019 16:00:33 +0530 Subject: [PATCH 116/841] phpcs fix --- .../Catalog/Api/ProductTierPriceManagementTest.php | 5 +++++ .../GroupedProduct/Pricing/Price/FinalPriceTest.php | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php index 098a1fde766c5..79afe5a50863d 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php @@ -9,6 +9,11 @@ use Magento\TestFramework\TestCase\WebapiAbstract; +/** + * Class ProductTierPriceManagementTest + * + * @package Magento\Catalog\Api + */ class ProductTierPriceManagementTest extends WebapiAbstract { const SERVICE_NAME = 'catalogProductTierPriceManagementV1'; diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php index c2c63d3f13892..5b8b73962288e 100644 --- a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php @@ -41,9 +41,11 @@ public function testFinalPriceWithTierPrice() /** @var $simpleProduct \Magento\Catalog\Api\Data\ProductInterface */ $simpleProduct = $productRepository->get('simple'); - $simpleProduct->setTierPrices([ - $tierPrice - ]); + $simpleProduct->setTierPrices( + [ + $tierPrice + ] + ); $productRepository->save($simpleProduct); /** @var $product \Magento\Catalog\Model\Product */ From 4ad28850d49555df4a6a55919a614a2bf2c2047c Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 15 Jul 2019 16:25:20 +0530 Subject: [PATCH 117/841] phpcs fix --- app/code/Magento/Catalog/Model/Product/Type/Price.php | 2 +- .../Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index c2e261f04a090..7978425adbdc4 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -502,7 +502,7 @@ public function getFormattedTierPrice($qty, $product) /** * Get formatted by currency tier price * - * @param float $qty + * @param float $qty * @param Product $product * * @return array|float diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php index 5b8b73962288e..63cb8effa2338 100644 --- a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php @@ -9,6 +9,11 @@ use Magento\Catalog\Api\Data\ProductTierPriceInterface; use Magento\TestFramework\Helper\Bootstrap; +/** + * Class FinalPriceTest + * + * @package Magento\GroupedProduct\Pricing\Price + */ class FinalPriceTest extends \PHPUnit\Framework\TestCase { /** From 93265e6495bc881b32827c7da4b02a21a610163f Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 15 Jul 2019 16:50:16 +0530 Subject: [PATCH 118/841] phpcs fix --- app/code/Magento/Catalog/Model/Product/Type/Price.php | 7 ++++--- .../GroupedProduct/Pricing/Price/FinalPriceTest.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index 7978425adbdc4..513c49db689e6 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -499,13 +499,14 @@ public function getFormattedTierPrice($qty, $product) return $price; } + /** * Get formatted by currency tier price * - * @param float $qty - * @param Product $product + * @param float $qty + * @param $product * - * @return array|float + * @return array|float * * @deprecated * @see getFormattedTierPrice() diff --git a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php index 63cb8effa2338..b5d9cb89356f6 100644 --- a/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php +++ b/dev/tests/integration/testsuite/Magento/GroupedProduct/Pricing/Price/FinalPriceTest.php @@ -11,7 +11,7 @@ /** * Class FinalPriceTest - * + * * @package Magento\GroupedProduct\Pricing\Price */ class FinalPriceTest extends \PHPUnit\Framework\TestCase From 09b94a65e1877a5e18832c8a8c26bc79f144f479 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 15 Jul 2019 17:15:54 +0530 Subject: [PATCH 119/841] phpcs fix --- app/code/Magento/Catalog/Model/Product/Type/Price.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index 513c49db689e6..4342b80d1e954 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -499,15 +499,12 @@ public function getFormattedTierPrice($qty, $product) return $price; } - /** * Get formatted by currency tier price * * @param float $qty - * @param $product - * + * @param Product $product * @return array|float - * * @deprecated * @see getFormattedTierPrice() */ From ca9de510637b0e5c1d2a9f9a0ebe882ec97af7a9 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 15 Jul 2019 17:45:39 +0530 Subject: [PATCH 120/841] phpcs fix --- app/code/Magento/Catalog/Model/Product/Type/Price.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index 4342b80d1e954..5d4a5c08db8f9 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -527,8 +527,8 @@ public function getFormattedPrice($product) /** * Get formatted by currency product price * - * @param Product $product - * @return array || float + * @param Product $product + * @return array|float * * @deprecated * @see getFormattedPrice() From 8d90e19f49d350a48eb14426a88e283438663e8c Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 15 Jul 2019 18:42:00 +0530 Subject: [PATCH 121/841] undefine variable fixed --- .../Magento/Catalog/Api/ProductTierPriceManagementTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php index 79afe5a50863d..8531bb8a24159 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductTierPriceManagementTest.php @@ -46,8 +46,8 @@ public function testGetList($customerGroupId, $count, $value, $qty) $this->assertCount($count, $tierPriceList); if ($count) { - $this->assertEquals($value, $tearPriceList[0]['value']); - $this->assertEquals($qty, $tearPriceList[0]['qty']); + $this->assertEquals($value, $tierPriceList[0]['value']); + $this->assertEquals($qty, $tierPriceList[0]['qty']); } } From a06c378c3d56973b299ee06e7cdac8299d41e5f5 Mon Sep 17 00:00:00 2001 From: Dzmitry Tabusheu <dzmitry_tabusheu@epam.com> Date: Tue, 16 Jul 2019 14:54:23 +0300 Subject: [PATCH 122/841] MAGETWO-45666: "Refresh cache" message is not displayed when changing category page layout - Fixed wrong design change detection when default category layout is changed --- .../Catalog/Model/CategoryRepository.php | 2 - .../InvalidateCacheOnCategoryDesignChange.php | 80 ++++++++++++++++++- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Model/CategoryRepository.php b/app/code/Magento/Catalog/Model/CategoryRepository.php index d5f6c58f9e6d1..312fd55aac01b 100644 --- a/app/code/Magento/Catalog/Model/CategoryRepository.php +++ b/app/code/Magento/Catalog/Model/CategoryRepository.php @@ -107,8 +107,6 @@ public function save(\Magento\Catalog\Api\Data\CategoryInterface $category) $parentCategory = $this->get($parentId, $storeId); $existingData['path'] = $parentCategory->getPath(); $existingData['parent_id'] = $parentId; - $existingData['custom_apply_to_products'] = $existingData['custom_apply_to_products'] ?? 0; - $existingData['custom_use_parent_settings'] = $existingData['custom_use_parent_settings'] ?? 0; } $category->addData($existingData); try { diff --git a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php index 1e57bb50eaf4d..08c7a03e443a9 100644 --- a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php +++ b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php @@ -15,12 +15,48 @@ */ class InvalidateCacheOnCategoryDesignChange implements ObserverInterface { + /** + * Default category design attributes values + */ + private $defaultAttributeValues; + + /** + * @var \Magento\Framework\App\Cache\TypeListInterface + */ + private $cacheTypeList; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + private $scopeConfig; + /** * @param \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig */ - public function __construct(\Magento\Framework\App\Cache\TypeListInterface $cacheTypeList) - { + public function __construct( + \Magento\Framework\App\Cache\TypeListInterface $cacheTypeList, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + ) { $this->cacheTypeList = $cacheTypeList; + $this->scopeConfig = $scopeConfig; + } + + /** + * Get default category design attribute values + * + * @return array + */ + private function getDefaultAttributeValues() + { + return [ + 'custom_apply_to_products' => '0', + 'custom_use_parent_settings' => '0', + 'page_layout' => $this->scopeConfig->getValue( + 'web/default_layouts/default_category_layout', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ) + ]; } /** @@ -33,7 +69,7 @@ public function execute(Observer $observer) $category = $observer->getEvent()->getEntity(); if (!$category->isObjectNew()) { foreach ($category->getDesignAttributes() as $designAttribute) { - if ($category->dataHasChangedFor($designAttribute->getAttributeCode())) { + if ($this->isCategoryAttributeChanged($designAttribute->getAttributeCode(), $category)) { $this->cacheTypeList->invalidate( [ \Magento\PageCache\Model\Cache\Type::TYPE_IDENTIFIER, @@ -45,4 +81,42 @@ public function execute(Observer $observer) } } } + + /** + * Check if category attribute changed + * + * @param string $attributeCode + * @param \Magento\Catalog\Api\Data\CategoryInterface $category + * @return bool + */ + private function isCategoryAttributeChanged($attributeCode, $category) + { + if (!array_key_exists($attributeCode, $category->getOrigData())) { + $defaultValue = $this->getDefaultAttributeValue($attributeCode); + if ($category->getData($attributeCode) !== $defaultValue) { + return true; + } + } else { + if ($category->dataHasChangedFor($attributeCode)) { + return true; + } + } + + return false; + } + + /** + * Get default category design attribute value + * + * @param string $attributeCode + * @return mixed|null + */ + private function getDefaultAttributeValue($attributeCode) + { + if ($this->defaultAttributeValues === null) { + $this->defaultAttributeValues = $this->getDefaultAttributeValues(); + } + + return $this->defaultAttributeValues[$attributeCode] ?? null; + } } From c09c17e3b7c3d9a86cf650aa8b642cb632d29013 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Tue, 16 Jul 2019 22:25:15 +0000 Subject: [PATCH 123/841] Created Config Module for Newsletter --- .../Model/Resolver/CreateCustomer.php | 29 ++++------ app/code/Magento/Newsletter/Model/Config.php | 53 +++++++++++++++++++ 2 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 app/code/Magento/Newsletter/Model/Config.php diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index 848f418e85917..daf37027f2d16 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -14,23 +14,13 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Store\Model\ScopeInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Newsletter\Model\Config; /** * Create customer account resolver */ class CreateCustomer implements ResolverInterface { - /** - * Configuration path to newsletter active setting - */ - const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; - - /** - * @var ScopeConfigInterface - */ - private $scopeConfig; - /** * @var ExtractCustomerData */ @@ -41,19 +31,24 @@ class CreateCustomer implements ResolverInterface */ private $createCustomerAccount; + /** + * @var Config + */ + private $config; + /** * CreateCustomer constructor. * * @param ExtractCustomerData $extractCustomerData * @param CreateCustomerAccount $createCustomerAccount - * @param ScopeConfigInterface $scopeConfig + * @param Config $config */ public function __construct( ExtractCustomerData $extractCustomerData, CreateCustomerAccount $createCustomerAccount, - ScopeConfigInterface $scopeConfig + Config $config ) { - $this->scopeConfig = $scopeConfig; + $this->config = $config; $this->extractCustomerData = $extractCustomerData; $this->createCustomerAccount = $createCustomerAccount; } @@ -72,11 +67,7 @@ public function resolve( throw new GraphQlInputException(__('"input" value should be specified')); } - if (!$this->scopeConfig->getValue( - self::XML_PATH_NEWSLETTER_ACTIVE, - ScopeInterface::SCOPE_STORE - ) - ) { + if (!$this->config->isActive()) { $args['input']['is_subscribed'] = false; } diff --git a/app/code/Magento/Newsletter/Model/Config.php b/app/code/Magento/Newsletter/Model/Config.php new file mode 100644 index 0000000000000..0da18b661399d --- /dev/null +++ b/app/code/Magento/Newsletter/Model/Config.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Newsletter\Model; + +/** + * Newsletter configuration model + */ +class Config +{ + /** + * Configuration path to newsletter active setting + */ + const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; + + /** + * @var \Magento\Framework\App\Config\ScopeConfigInterface + */ + protected $scopeConfig; + + /** + * @var \Magento\Config\Model\ResourceModel\Config + */ + protected $resourceConfig; + + /** + * Config constructor. + * + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Config\Model\ResourceModel\Config $resourceConfig + */ + public function __construct( + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Config\Model\ResourceModel\Config $resourceConfig + ) { + $this->scopeConfig = $scopeConfig; + $this->resourceConfig = $resourceConfig; + } + + /** + * Returns newsletter's enabled status + * + * @return bool + */ + public function isActive() + { + return $this->scopeConfig->isSetFlag(self::XML_PATH_NEWSLETTER_ACTIVE); + } +} From 3e12e38b9e25f34ff5fd56ba5740c7fb5301494b Mon Sep 17 00:00:00 2001 From: Dzmitry Tabusheu <dzmitry_tabusheu@epam.com> Date: Wed, 17 Jul 2019 12:05:09 +0300 Subject: [PATCH 124/841] MAGETWO-45666: "Refresh cache" message is not displayed when changing category page layout - Fixed wrong design change detection when default category layout is changed --- .../InvalidateCacheOnCategoryDesignChange.php | 22 +------------------ 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php index 08c7a03e443a9..0b30ff4f1c038 100644 --- a/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php +++ b/app/code/Magento/Catalog/Observer/InvalidateCacheOnCategoryDesignChange.php @@ -15,11 +15,6 @@ */ class InvalidateCacheOnCategoryDesignChange implements ObserverInterface { - /** - * Default category design attributes values - */ - private $defaultAttributeValues; - /** * @var \Magento\Framework\App\Cache\TypeListInterface */ @@ -92,7 +87,7 @@ public function execute(Observer $observer) private function isCategoryAttributeChanged($attributeCode, $category) { if (!array_key_exists($attributeCode, $category->getOrigData())) { - $defaultValue = $this->getDefaultAttributeValue($attributeCode); + $defaultValue = $this->getDefaultAttributeValues()[$attributeCode] ?? null; if ($category->getData($attributeCode) !== $defaultValue) { return true; } @@ -104,19 +99,4 @@ private function isCategoryAttributeChanged($attributeCode, $category) return false; } - - /** - * Get default category design attribute value - * - * @param string $attributeCode - * @return mixed|null - */ - private function getDefaultAttributeValue($attributeCode) - { - if ($this->defaultAttributeValues === null) { - $this->defaultAttributeValues = $this->getDefaultAttributeValues(); - } - - return $this->defaultAttributeValues[$attributeCode] ?? null; - } } From 8966de20bbe6a5c938a701eab765e364f3e0caf8 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Wed, 17 Jul 2019 11:05:36 +0300 Subject: [PATCH 125/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" - Update automated test --- .../AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml index 350add2862ce7..85ce0e320bc1c 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml @@ -25,7 +25,10 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <!--Set PayPal Payments Standard Configs--> <comment userInput="Set PayPal Payments Standard Configs" stepKey="commentsetConfigs"/> - // TODO: set PayPal credentials + <magentoCLI command="config:set paypal/wpp/api_authentication 0" stepKey="setApiAuthentication"/> + <magentoCLI command="config:set paypal/wpp/api_username username" stepKey="setApiUserName"/> + <magentoCLI command="config:set paypal/wpp/api_password password" stepKey="setApiPassword"/> + <magentoCLI command="config:set paypal/wpp/api_signature signature" stepKey="setApiSignature"/> <magentoCLI command="config:set paypal/wpp/sandbox_flag 1" stepKey="setSandBox"/> <magentoCLI command="config:set paypal/wpp/use_proxy 0" stepKey="setUseProxy"/> <magentoCLI command="config:set payment/wps_express/active 1" stepKey="enableWPSExpress"/> @@ -40,13 +43,13 @@ <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> <conditionalClick selector="{{OtherPayPalPaymentsConfigSection.expandTab('us')}}" dependentSelector="{{OtherPayPalPaymentsConfigSection.expandedTab('us')}}" visible="false" stepKey="clickOtherPayPalPaymentsSection"/> - <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckofut"> + <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckout"> <argument name="payPalConfigType" value="WPSExpressConfigSection"/> <argument name="countryCode" value="us"/> </actionGroup> <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForPageLoad2"/> - <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckout"> + <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckout2"> <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> <argument name="countryCode" value="us"/> </actionGroup> From ed7ce471751045357ec8a0cdcf5b9858625625c9 Mon Sep 17 00:00:00 2001 From: Lilit Sargsyan <Lilit_Sargsyan@epam.com> Date: Wed, 17 Jul 2019 17:45:08 +0400 Subject: [PATCH 126/841] MC-15341: Default product numbers to display results in poor display on Desktop - Updated automated test script --- .../CheckDefaultNumberProductsToDisplayTest.xml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml index 6fb4f816d0b9e..5718844fb6df3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml @@ -21,12 +21,6 @@ <!-- Login as Admin --> <comment userInput="Login as Admin" stepKey="commentLoginAsAdmin"/> <actionGroup ref="LoginAsAdmin" stepKey="login"/> - <!-- Set default configurations --> - <comment userInput="Set default configurations" stepKey="commentSetDefaultCategory"/> - <magentoCLI command="config:set catalog/frontend/grid_per_page_values 12,24,36" stepKey="setAllowedProductsPerPageValue"/> - <magentoCLI command="config:set catalog/frontend/grid_per_page 12" stepKey="setDefaultProductsPerPageValue"/> - <magentoCLI command="indexer:reindex" stepKey="reindex"/> - <magentoCLI command="cache:flush" stepKey="flushCache"/> <!--Create 37 Products and Subcategory --> <comment userInput="Create 37 Products and Subcategory" stepKey="commentCreateData"/> <createData entity="SimpleSubCategory" stepKey="createCategory"/> @@ -183,6 +177,14 @@ <deleteData createDataKey="createSimpleProductThirtySeven" stepKey="deleteProductThirtySeven"/> <actionGroup ref="logout" stepKey="logout"/> </after> + <!--Verify configuration for default number of products displayed in the grid view--> + <comment userInput="Verify configuration for default number of products displayed in the grid view" stepKey="commentVerifyDefaultValues"/> + <amOnPage url="{{CatalogConfigPage.url}}" stepKey="goToCatalogConfigPagePage"/> + <waitForPageLoad stepKey="waitForConfigPageLoad" /> + <conditionalClick selector="{{AdminCatalogStorefrontConfigSection.sectionHeader}}" dependentSelector="{{AdminCatalogStorefrontConfigSection.productsPerPageAllowedValues}}" visible="false" stepKey="openCatalogConfigStorefrontSection"/> + <waitForElementVisible selector="{{AdminCatalogStorefrontConfigSection.productsPerPageAllowedValues}}" stepKey="waitForSectionOpen"/> + <seeInField selector="{{AdminCatalogStorefrontConfigSection.productsPerPageAllowedValues}}" userInput="12,24,36" stepKey="seeDefaultValueAllowedNumberProductsPerPage"/> + <seeInField selector="{{AdminCatalogStorefrontConfigSection.productsPerPageDefaultValue}}" userInput="12" stepKey="seeDefaultValueProductPerPage"/> <!-- Open storefront on the category page --> <comment userInput="Open storefront on the category page" stepKey="commentOpenStorefront"/> <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="goToStorefrontCreatedCategoryPage"/> From 4ccf3068f612771dbc4c48969b37c7ac445b7159 Mon Sep 17 00:00:00 2001 From: Serhii Dzhepa <sdzhepa@adobe.com> Date: Wed, 17 Jul 2019 16:59:02 -0500 Subject: [PATCH 127/841] return old constant name due to Backward Compatibility Policy --- .../Model/Import/AdvancedPricing.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index 3e597c194a72b..d883173daeece 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -50,7 +50,7 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract const VALIDATOR_WEBSITE = 'validator_website'; - const VALIDATOR_TIER_PRICE = 'validator_tier_price'; + const VALIDATOR_TEAR_PRICE = 'validator_tier_price'; /** * Validation failure message template definitions. @@ -221,7 +221,7 @@ public function __construct( $this->_catalogProductEntity = $this->_resourceFactory->create()->getTable('catalog_product_entity'); $this->_oldSkus = $this->retrieveOldSkus(); $this->_validators[self::VALIDATOR_WEBSITE] = $websiteValidator; - $this->_validators[self::VALIDATOR_TIER_PRICE] = $tierPriceValidator; + $this->_validators[self::VALIDATOR_TEAR_PRICE] = $tierPriceValidator; $this->errorAggregator = $errorAggregator; foreach (array_merge($this->errorMessageTemplates, $this->_messageTemplates) as $errorCode => $message) { @@ -536,7 +536,7 @@ protected function getWebSiteId($websiteCode) */ protected function getCustomerGroupId($customerGroup) { - $customerGroups = $this->_getValidator(self::VALIDATOR_TIER_PRICE)->getCustomerGroups(); + $customerGroups = $this->_getValidator(self::VALIDATOR_TEAR_PRICE)->getCustomerGroups(); return $customerGroup == self::VALUE_ALL_GROUPS ? 0 : $customerGroups[$customerGroup]; } From 0f43b32c2d5a22bee305fa8bc211f835d4590b59 Mon Sep 17 00:00:00 2001 From: Ihor Sviziev <ihor-sviziev@users.noreply.github.com> Date: Thu, 18 Jul 2019 09:33:52 +0300 Subject: [PATCH 128/841] magento/magento2#22880 Fix random fails during parallel SCD execution Remove unit tests that were failing because we not doing anything in CLI mode --- .../Test/Unit/Asset/LockerProcessTest.php | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/LockerProcessTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/LockerProcessTest.php index aba3ff1099cc0..ca065bbbc4f6e 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/LockerProcessTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/LockerProcessTest.php @@ -64,24 +64,6 @@ protected function setUp() ); } - /** - * Test for lockProcess method - * - * @param string $method - * - * @dataProvider dataProviderTestLockProcess - */ - public function testLockProcess($method) - { - $this->stateMock->expects(self::once())->method('getMode')->willReturn(State::MODE_DEVELOPER); - $this->filesystemMock->expects(self::once()) - ->method('getDirectoryWrite') - ->with(DirectoryList::VAR_DIR) - ->willReturn($this->$method()); - - $this->lockerProcess->lockProcess(self::LOCK_NAME); - } - public function testNotLockProcessInProductionMode() { $this->stateMock->expects(self::once())->method('getMode')->willReturn(State::MODE_PRODUCTION); @@ -90,21 +72,6 @@ public function testNotLockProcessInProductionMode() $this->lockerProcess->lockProcess(self::LOCK_NAME); } - /** - * Test for unlockProcess method - */ - public function testUnlockProcess() - { - $this->stateMock->expects(self::exactly(2))->method('getMode')->willReturn(State::MODE_DEVELOPER); - $this->filesystemMock->expects(self::once()) - ->method('getDirectoryWrite') - ->with(DirectoryList::VAR_DIR) - ->willReturn($this->getTmpDirectoryMockFalse(1)); - - $this->lockerProcess->lockProcess(self::LOCK_NAME); - $this->lockerProcess->unlockProcess(); - } - public function testNotUnlockProcessInProductionMode() { $this->stateMock->expects(self::exactly(2))->method('getMode')->willReturn(State::MODE_PRODUCTION); @@ -114,17 +81,6 @@ public function testNotUnlockProcessInProductionMode() $this->lockerProcess->unlockProcess(); } - /** - * @return array - */ - public function dataProviderTestLockProcess() - { - return [ - ['method' => 'getTmpDirectoryMockTrue'], - ['method' => 'getTmpDirectoryMockFalse'] - ]; - } - /** * @return WriteInterface|\PHPUnit_Framework_MockObject_MockObject */ From 274f7f20a0de29a70bea4900b4c1bbc32bb40b4b Mon Sep 17 00:00:00 2001 From: kickDominic <49071863+kickDominic@users.noreply.github.com> Date: Thu, 18 Jul 2019 09:33:55 +0100 Subject: [PATCH 129/841] group_id overridden if set group_id overridden to default group if set. --- app/code/Magento/Customer/Model/CustomerExtractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Model/CustomerExtractor.php b/app/code/Magento/Customer/Model/CustomerExtractor.php index be30b97994787..debdd001d4f79 100644 --- a/app/code/Magento/Customer/Model/CustomerExtractor.php +++ b/app/code/Magento/Customer/Model/CustomerExtractor.php @@ -80,7 +80,7 @@ public function extract( $customerData = $customerForm->compactData($customerData); $allowedAttributes = $customerForm->getAllowedAttributes(); - $isGroupIdEmpty = isset($allowedAttributes['group_id']); + $isGroupIdEmpty = !isset($allowedAttributes['group_id']); $customerDataObject = $this->customerFactory->create(); $this->dataObjectHelper->populateWithArray( From 18c8be6e504c80955532d2aaed67670b31aac02d Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Mon, 15 Jul 2019 16:00:29 +0300 Subject: [PATCH 130/841] MC-16375: Unable to pay with Braintree Paypal (Payment Action = Authorize and Capture) for Gift Card as Guest - Fix braintree paypal quote address --- .../Plugin/DisableQuoteAddressValidation.php | 46 ++++++------------- app/code/Magento/Braintree/etc/di.xml | 2 + 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php b/app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php index c4e11a9eedb37..03117a4e977a1 100644 --- a/app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php +++ b/app/code/Magento/Braintree/Plugin/DisableQuoteAddressValidation.php @@ -7,53 +7,37 @@ namespace Magento\Braintree\Plugin; -use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Quote\Model\QuoteManagement; use Magento\Quote\Api\CartManagementInterface; +use Magento\Quote\Model\Quote; /** - * Plugin for CartManagementInterface to disable quote address validation + * Plugin for QuoteManagement to disable quote address validation */ class DisableQuoteAddressValidation { /** - * @var CartRepositoryInterface - */ - private $quoteRepository; - - /** - * @param CartRepositoryInterface $quoteRepository - */ - public function __construct( - CartRepositoryInterface $quoteRepository - ) { - $this->quoteRepository = $quoteRepository; - } - - /** - * Disable quote address validation before place order + * Disable quote address validation before submit order * - * @param CartManagementInterface $subject - * @param \Closure $proceed - * @param int $cartId - * @param PaymentInterface|null $payment - * @return int - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @param QuoteManagement $subject + * @param Quote $quote + * @param array $orderData + * @return array + * @throws \Exception + * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundPlaceOrder( - CartManagementInterface $subject, - \Closure $proceed, - int $cartId, - PaymentInterface $payment = null + public function beforeSubmit( + QuoteManagement $subject, + Quote $quote, + $orderData = [] ) { - $quote = $this->quoteRepository->get($cartId); if ($quote->getPayment()->getMethod() == 'braintree_paypal' && $quote->getCheckoutMethod() == CartManagementInterface::METHOD_GUEST) { $billingAddress = $quote->getBillingAddress(); $billingAddress->setShouldIgnoreValidation(true); $quote->setBillingAddress($billingAddress); } - return $proceed($cartId, $payment); + return [$quote, $orderData]; } } diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index ebb85767dec91..f6ad552fc9bef 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -626,6 +626,8 @@ <type name="Magento\Quote\Api\CartManagementInterface"> <plugin name="order_cancellation" type="Magento\Braintree\Plugin\OrderCancellation"/> + </type> + <type name="Magento\Quote\Model\QuoteManagement"> <plugin name="disable_quote_address_validation" type="Magento\Braintree\Plugin\DisableQuoteAddressValidation"/> </type> </config> From 5398c33f042d788b1d491410e8cce7864c197e9b Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Mon, 22 Jul 2019 11:14:17 +0300 Subject: [PATCH 131/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" - Unskip test --- .../Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml index 85ce0e320bc1c..a5a0e7f27afd4 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml @@ -17,9 +17,6 @@ <testCaseId value="MC-17776"/> <useCaseId value=" MC-15140"/> <group value="paypal"/> - <skip> - <issueId value="MQE-1578"/> - </skip> </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> From 3a45e91c243820f92bab26ca645ff647e7d4c68b Mon Sep 17 00:00:00 2001 From: Tan Sezer <t.sezer@youwe.nl> Date: Mon, 22 Jul 2019 12:09:58 +0200 Subject: [PATCH 132/841] Replace Serializer with Json serializer Add new mock for Json Serializer --- .../CleanConfigurationTmpImages.php | 6 +++--- .../CleanConfigurationTmpImagesTest.php | 17 +++++++++++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php index 946ef761dd22e..1d088d0ac043f 100644 --- a/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php +++ b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php @@ -39,7 +39,7 @@ class CleanConfigurationTmpImages private $request; /** - * @var \Magento\Framework\Serialize\Serializer\Serialize + * @var \Magento\Framework\Serialize\Serializer\Json */ private $serialize; @@ -48,7 +48,7 @@ class CleanConfigurationTmpImages * @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig * @param \Magento\Framework\Filesystem $filesystem - * @param \Magento\Framework\Serialize\Serializer\Serialize $serialize + * @param \Magento\Framework\Serialize\Serializer\Json $serialize * * @throws \Magento\Framework\Exception\FileSystemException */ @@ -57,7 +57,7 @@ public function __construct( \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb, \Magento\Catalog\Model\Product\Media\Config $mediaConfig, \Magento\Framework\Filesystem $filesystem, - \Magento\Framework\Serialize\Serializer\Serialize $serialize + \Magento\Framework\Serialize\Serializer\Json $serialize ) { $this->request = $request; $this->fileStorageDb = $fileStorageDb; diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php index 8026dffbe13fe..7f18da7e314e8 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Product/Initialization/CleanConfigurationTmpImagesTest.php @@ -12,6 +12,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\Write; +use Magento\Framework\Serialize\Serializer\Json; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\MediaStorage\Helper\File\Storage\Database as FileStorage; @@ -58,6 +59,11 @@ class CleanConfigurationTmpImagesTest extends \PHPUnit\Framework\TestCase */ private $writeFolder; + /** + * @var Json|\PHPUnit_Framework_MockObject_MockObject + */ + private $seralizer; + /** * @var ProductInitializationHelper|\PHPUnit_Framework_MockObject_MockObject */ @@ -79,6 +85,9 @@ protected function setUp() $this->writeFolder = $this->getMockBuilder(Write::class) ->disableOriginalConstructor() ->getMock(); + $this->seralizer = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); $this->subjectMock = $this->getMockBuilder(ProductInitializationHelper::class) ->disableOriginalConstructor() ->getMock(); @@ -94,7 +103,8 @@ protected function setUp() 'request' => $this->requestMock, 'fileStorageDb' => $this->fileStorageDb, 'mediaConfig' => $this->mediaConfig, - 'filesystem' => $this->filesystem + 'filesystem' => $this->filesystem, + 'seralizer' => $this->seralizer ] ); } @@ -163,7 +173,10 @@ public function testAfterInitialize() ] ); - $this->assertSame($productMock, $this->cleanConfigurationTmpImages->afterInitialize($this->subjectMock, $productMock)); + $this->assertSame( + $productMock, + $this->cleanConfigurationTmpImages->afterInitialize($this->subjectMock, $productMock) + ); } /** From 7bed43170c4d15af5f1836b6002499cdffe812ff Mon Sep 17 00:00:00 2001 From: Geeta <geeta@ranosys.com> Date: Thu, 18 Jul 2019 14:37:48 +0530 Subject: [PATCH 133/841] Fix(21650): Added the test-cases for number validation --- .../view/base/web/js/lib/validation/rules.js | 2 +- .../Ui/base/js/lib/validation/rules.test.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index 6f0dd01f33291..f0831a7224951 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -652,7 +652,7 @@ define([ 'validate-number': [ function (value) { return utils.isEmptyNoTrim(value) || - !isNaN(utils.parseNumber(value)) && /^\s*-?\d*(,\d*)*(\.\d*)?\s*$/.test(value); + !isNaN(utils.parseNumber(value)) && /^\s*-?\d{1,}(?:[.,|'|\s]\d{1,})*(?:[.,|'|\s]\d{2})?-?\s*$/.test(value); }, $.mage.__('Please enter a valid number in this field.') ], diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js index 692c843d18e92..976e43ad01dc6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js @@ -63,7 +63,25 @@ define([ expect(rules['validate-number'].handler(value)).toBe(true); }); + + it('Check on space', function () { + var value = '10 000'; + expect(rules['validate-number'].handler(value)).toBe(true); + }); + + it('Check on formatted float (For International price)', function () { + var value = '10.000,00'; + + expect(rules['validate-number'].handler(value)).toBe(true); + }); + + it('Check on formatted float (For International price)', function () { + var value = "10'000.00"; + + expect(rules['validate-number'].handler(value)).toBe(true); + }); + it('Check on not a number', function () { var value = 'string'; From 2454e0cbc34dd147d75ed14565513d1ded5d0ff8 Mon Sep 17 00:00:00 2001 From: Jeroen <jeroen@reachdigital.nl> Date: Mon, 22 Jul 2019 17:15:44 +0200 Subject: [PATCH 134/841] Allow null values for integer columns --- .../Setup/Declaration/Schema/Dto/Factories/Integer.php | 2 +- .../Declaration/Schema/etc/types/integers/integer.xsd | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php index 5cd7bcac39736..e33cd0927d473 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php @@ -63,7 +63,7 @@ public function create(array $data) } if (isset($data['default'])) { - $data['default'] = (int) $data['default']; + $data['default'] = $data['default'] !== 'null' ? (int) $data['default'] : null; } return $this->objectManager->create($this->className, $data); diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/integers/integer.xsd b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/integers/integer.xsd index e264060cba63d..ff68beb27191d 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/integers/integer.xsd +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/types/integers/integer.xsd @@ -17,7 +17,13 @@ Size is 4 bytes. </xs:documentation> </xs:annotation> - <xs:attribute name="default" type="xs:integer" /> + <xs:attribute name="default"> + <xs:simpleType> + <xs:restriction base="xs:string"> + <xs:pattern value="\d+|null" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> <xs:attribute name="padding"> <xs:annotation> <xs:documentation> From 089bebcf1ef29b13e61cf696d63cf346cb274d56 Mon Sep 17 00:00:00 2001 From: Sergey Solo <ssolomakhin@magecom.us> Date: Mon, 22 Jul 2019 18:28:55 +0300 Subject: [PATCH 135/841] m2-github-issue-3993; Fix same level categories (position)re-index on category move. --- app/code/Magento/Catalog/Model/Category.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index d911bec0aaac9..fe26cd90655d1 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -433,7 +433,9 @@ public function move($parentId, $afterCategoryId) if ($this->flatState->isFlatEnabled()) { $flatIndexer = $this->indexerRegistry->get(Indexer\Category\Flat\State::INDEXER_ID); if (!$flatIndexer->isScheduled()) { - $flatIndexer->reindexList([$this->getId(), $oldParentId, $parentId]); + $sameLevelCategories = explode(',', $this->getParentCategory()->getChildren()); + $list = array_unique(array_merge($sameLevelCategories, [$this->getId(), $oldParentId, $parentId])); + $flatIndexer->reindexList($list); } } $productIndexer = $this->indexerRegistry->get(Indexer\Category\Product::INDEXER_ID); From 8fce4bffa98b663f3218e86831827ac859429ab7 Mon Sep 17 00:00:00 2001 From: natalia <natalia_marozava@epam.com> Date: Wed, 24 Jul 2019 12:20:48 +0300 Subject: [PATCH 136/841] MC-17251: Creating a preference for category product indexer breaks setup:di:compile - Added more common fix - Reverted changes in Magento\Catalog\Model\Indexer\Category\Product\Action\Full --- .../Indexer/Category/Product/Action/Full.php | 4 ++-- .../Interception/Code/Generator/Interceptor.php | 16 ++++++++++++---- .../ObjectManager/Code/Generator/Proxy.php | 15 +++++++++++---- 3 files changed, 25 insertions(+), 10 deletions(-) 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 8117cf7e45ae5..eb59acb56c356 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 @@ -150,9 +150,9 @@ private function switchTables(): void /** * Refresh entities index * - * @return AbstractAction + * @return $this */ - public function execute(): AbstractAction + public function execute(): self { $this->createTables(); $this->clearReplicaTables(); diff --git a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php index 9297ca25928d3..624dc21759640 100644 --- a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\Interception\Code\Generator; /** @@ -58,6 +60,7 @@ protected function _getDefaultConstructorDefinition() ? "parent::__construct({$this->_getParameterList($parameters)});" : "parent::__construct();"; } + return [ 'name' => '__construct', 'parameters' => $parameters, @@ -81,6 +84,7 @@ protected function _getClassMethods() $methods[] = $this->_getMethodInfo($method); } } + return $methods; } @@ -109,7 +113,7 @@ protected function _getMethodInfo(\ReflectionMethod $method) $parameters[] = $this->_getMethodParameterInfo($parameter); } - $returnTypeValue = $this->getReturnTypeValue($method->getReturnType()); + $returnTypeValue = $this->getReturnTypeValue($method); $methodInfo = [ 'name' => ($method->returnsReference() ? '& ' : '') . $method->getName(), 'parameters' => $parameters, @@ -184,6 +188,7 @@ protected function _generateCode() $this->_classGenerator->addTrait('\\' . \Magento\Framework\Interception\Interceptor::class); $interfaces[] = '\\' . \Magento\Framework\Interception\InterceptorInterface::class; $this->_classGenerator->setImplementedInterfaces($interfaces); + return parent::_generateCode(); } @@ -211,24 +216,27 @@ protected function _validateData() $result = false; } } + return $result; } /** * Returns return type * - * @param mixed $returnType + * @param \ReflectionMethod $method * @return null|string */ - private function getReturnTypeValue($returnType): ?string + private function getReturnTypeValue(\ReflectionMethod $method): ?string { $returnTypeValue = null; + $returnType = $method->getReturnType(); if ($returnType) { $returnTypeValue = ($returnType->allowsNull() ? '?' : ''); $returnTypeValue .= ($returnType->getName() === 'self') - ? $this->getSourceClassName() + ? $this->_getFullyQualifiedClassName($method->getDeclaringClass()->getName()) : $returnType->getName(); } + return $returnTypeValue; } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php index efa1b4be60ced..8d69cce209c00 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php @@ -6,6 +6,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\ObjectManager\Code\Generator; /** @@ -73,6 +75,7 @@ protected function _getClassProperties() 'tags' => [['name' => 'var', 'description' => 'bool']], ], ]; + return $properties; } @@ -154,6 +157,7 @@ protected function _generateCode() $this->_classGenerator->setExtendedClass($typeName); $this->_classGenerator->setImplementedInterfaces(['\\' . self::NON_INTERCEPTABLE_INTERFACE]); } + return parent::_generateCode(); } @@ -173,7 +177,7 @@ protected function _getMethodInfo(\ReflectionMethod $method) $parameters[] = $this->_getMethodParameterInfo($parameter); } - $returnTypeValue = $this->getReturnTypeValue($method->getReturnType()); + $returnTypeValue = $this->getReturnTypeValue($method); $methodInfo = [ 'name' => $method->getName(), 'parameters' => $parameters, @@ -269,24 +273,27 @@ protected function _validateData() $result = false; } } + return $result; } /** * Returns return type * - * @param mixed $returnType + * @param \ReflectionMethod $method * @return null|string */ - private function getReturnTypeValue($returnType): ?string + private function getReturnTypeValue(\ReflectionMethod $method): ?string { $returnTypeValue = null; + $returnType = $method->getReturnType(); if ($returnType) { $returnTypeValue = ($returnType->allowsNull() ? '?' : ''); $returnTypeValue .= ($returnType->getName() === 'self') - ? $this->getSourceClassName() + ? $this->_getFullyQualifiedClassName($method->getDeclaringClass()->getName()) : $returnType->getName(); } + return $returnTypeValue; } } From 04739de66cf39fe6f1de50cf93dd05471ec03c7d Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Fri, 26 Jul 2019 17:14:04 +0300 Subject: [PATCH 137/841] MAGETWO-44170: Not pass function test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Added automated test script. --- .../Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index b1af17fb25838..cac5e209af9fd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -395,6 +395,7 @@ <!--Assert configurable product on Admin product page grid--> <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfig"/> + <click selector="{{AdminProductGridSection.columnHeader('ID')}}" stepKey="clickIdHeaderToSortAsc"/> <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuFroConfig"> <argument name="sku" value="$$createProduct.sku$$"/> </actionGroup> From 3a403eabe8f87a009bd615a666ca01724c4d1b9d Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Sat, 27 Jul 2019 18:47:04 +0200 Subject: [PATCH 138/841] Fixed some naming issues --- app/code/Magento/CustomerDownloadableGraphQl/composer.json | 2 +- .../Magento/CustomerDownloadableGraphQl/etc/schema.graphqls | 2 +- app/code/Magento/CustomerDownloadableGraphQl/registration.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CustomerDownloadableGraphQl/composer.json b/app/code/Magento/CustomerDownloadableGraphQl/composer.json index cb9b8bb89d53e..de2ef4d133242 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/composer.json +++ b/app/code/Magento/CustomerDownloadableGraphQl/composer.json @@ -22,7 +22,7 @@ "registration.php" ], "psr-4": { - "Magento\\DownloadableGraphQl\\": "" + "Magento\\CustomerDownloadableGraphQl\\": "" } } } diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls index d4544605f241d..eafd762329e81 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false) + customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\CustomerDownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false) } type CustomerDownloadableProducts { diff --git a/app/code/Magento/CustomerDownloadableGraphQl/registration.php b/app/code/Magento/CustomerDownloadableGraphQl/registration.php index 3308d79f92458..c029446bb5651 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/registration.php +++ b/app/code/Magento/CustomerDownloadableGraphQl/registration.php @@ -6,4 +6,4 @@ use Magento\Framework\Component\ComponentRegistrar; -ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_DownloadableGraphQl', __DIR__); +ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_CustomerDownloadableGraphQl', __DIR__); From d42c43b2f3b1b9e940c0a046a270bff8e6966155 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Sat, 27 Jul 2019 18:48:16 +0200 Subject: [PATCH 139/841] Moved resolver class --- .../Resolver/CustomerDownloadableProducts.php | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php diff --git a/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php b/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php new file mode 100644 index 0000000000000..e3a4f49abb4a8 --- /dev/null +++ b/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DownloadableGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Catalog\Model\Product; +use Magento\Downloadable\Helper\Data as DownloadableHelper; +use Magento\Downloadable\Model\Product\Type as Downloadable; +use Magento\Downloadable\Model\ResourceModel\Link\Collection as LinkCollection; +use Magento\Downloadable\Model\ResourceModel\Sample\Collection as SampleCollection; +use Magento\Framework\Data\Collection; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\EnumLookup; +use Magento\Framework\GraphQl\Query\ResolverInterface; + +/** + * @inheritdoc + * + * Format for downloadable product types + */ +class CustomerDownloadableProducts implements ResolverInterface +{ + /** + * @var EnumLookup + */ + private $enumLookup; + + /** + * @var DownloadableHelper + */ + private $downloadableHelper; + + /** + * @var SampleCollection + */ + private $sampleCollection; + + /** + * @var LinkCollection + */ + private $linkCollection; + + /** + * @param EnumLookup $enumLookup + * @param DownloadableHelper $downloadableHelper + * @param SampleCollection $sampleCollection + * @param LinkCollection $linkCollection + */ + public function __construct( + EnumLookup $enumLookup, + DownloadableHelper $downloadableHelper, + SampleCollection $sampleCollection, + LinkCollection $linkCollection + ) { + $this->enumLookup = $enumLookup; + $this->downloadableHelper = $downloadableHelper; + $this->sampleCollection = $sampleCollection; + $this->linkCollection = $linkCollection; + } + + /** + * @inheritdoc + * + * Add downloadable options to configurable types + * + * @param \Magento\Framework\GraphQl\Config\Element\Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @throws \Exception + * @return null|array + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + die('aaaa'); + return [1]; + } +} From 59e452f194049063bdda32f51230ebe4b28e4852 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Sat, 27 Jul 2019 18:49:17 +0200 Subject: [PATCH 140/841] Started with api-functional tests for customer downloadable products --- .../CustomerDownloadableProductTest.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php new file mode 100644 index 0000000000000..18355f4023549 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\CustomerDownloadableProduct; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class CustomerDownloadableProductTest extends GraphQlAbstract +{ + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php + */ + public function testGetCustomerDownloadableProducts() + { + $query = <<<MUTATION +mutation { + generateCustomerToken( + email: "customer@example.com" + password: "password" + ) { + token + } +} +MUTATION; + $response = $this->graphQlMutation($query); + $token = $response['generateCustomerToken']['token']; + $this->headers = ['Authorization' => 'Bearer ' . $token]; + + $query = <<<QUERY + { + customerDownloadableProducts{ + items{ + order_increment_id + date + status + download_url + remaining_downloads + } + } + +} +QUERY; + + $response = $this->graphQlQuery($query, [], '', $this->headers); + } +} From f55229e7565ff56d5eb6e7f5506d6618f95b6212 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Mon, 29 Jul 2019 19:24:17 +0200 Subject: [PATCH 141/841] API-functional tests progress --- .../CustomerDownloadableProductTest.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php index 18355f4023549..062d88defee74 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php @@ -8,13 +8,19 @@ namespace Magento\GraphQl\CustomerDownloadableProduct; use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\TestFramework\ObjectManager; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; class CustomerDownloadableProductTest extends GraphQlAbstract { + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php + * @magentoApiDataFixture Magento/Downloadable/_files/order_with_downloadable_product.php */ public function testGetCustomerDownloadableProducts() { @@ -46,7 +52,49 @@ public function testGetCustomerDownloadableProducts() } QUERY; + $objectManager = ObjectManager::getInstance(); + + $searchCriteria = $objectManager->get(SearchCriteriaBuilder::class)->create(); + + $orderRepository = $objectManager->create(OrderRepositoryInterface::class); + $orders = $orderRepository->getList($searchCriteria)->getItems(); + $order = array_pop($orders); + + $builder = $objectManager->create(\Magento\Framework\Api\FilterBuilder::class); + $filter = $builder + ->setField('email') + ->setValue('customer@example.com'); + + $searchCriteria = $objectManager->get( + SearchCriteriaBuilder::class + )->addFilter( + 'email', + 'customer@example.com' + )->create(); + + $customerRepository = $objectManager->create(CustomerRepositoryInterface::class); + $customers = $customerRepository->getList($searchCriteria) + ->getItems(); + $customer = array_pop($customers); + + $order->setCustomerId($customer->getId())->setCustomerIsGuest(false)->save(); $response = $this->graphQlQuery($query, [], '', $this->headers); + + $expectedResponse = [ + 'customerDownloadableProducts' => [ + 'items' => [ + [ + 'order_increment_id' => $order->getIncrementId(), + 'date' => '', + 'status' => 'pending', + 'download_url' => 'http://example.com/downloadable.txt', + 'remaining_downloads' => '1' + ] + ] + ] + ]; + + $this->assertEquals($expectedResponse, $response); } } From a4da0fd5ea8674b5426ab07906e11628ae645427 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Mon, 29 Jul 2019 19:25:46 +0200 Subject: [PATCH 142/841] Removed some code from API-functional tests --- .../CustomerDownloadableProductTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php index 062d88defee74..fe3115263dfd4 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php @@ -60,11 +60,6 @@ public function testGetCustomerDownloadableProducts() $orders = $orderRepository->getList($searchCriteria)->getItems(); $order = array_pop($orders); - $builder = $objectManager->create(\Magento\Framework\Api\FilterBuilder::class); - $filter = $builder - ->setField('email') - ->setValue('customer@example.com'); - $searchCriteria = $objectManager->get( SearchCriteriaBuilder::class )->addFilter( @@ -78,7 +73,6 @@ public function testGetCustomerDownloadableProducts() $customer = array_pop($customers); $order->setCustomerId($customer->getId())->setCustomerIsGuest(false)->save(); - $response = $this->graphQlQuery($query, [], '', $this->headers); $expectedResponse = [ From 4d380bf488e027047833d270bb7d816d78d0f43f Mon Sep 17 00:00:00 2001 From: Gareth James <gaz.e.james@gmail.com> Date: Wed, 31 Jul 2019 00:37:25 +0100 Subject: [PATCH 143/841] Added phpcs disable of Magento2.Classes.AbstractApi to Magento\Catalog\Model\ResourceModel\AbstractResource for static tests --- .../Magento/Catalog/Model/ResourceModel/AbstractResource.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php index b7665c176dfad..3946be32184ec 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -15,6 +15,7 @@ /** * Catalog entity abstract model * + * phpcs:disable Magento2.Classes.AbstractApi * @api * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) From 796ce7fdfc9db07b3a4df8a5abea9f3e09cb5797 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Wed, 31 Jul 2019 13:07:35 +0200 Subject: [PATCH 144/841] API-functional test change --- .../CustomerDownloadableProductTest.php | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php index fe3115263dfd4..ce0d830a0e9fc 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php @@ -75,20 +75,9 @@ public function testGetCustomerDownloadableProducts() $order->setCustomerId($customer->getId())->setCustomerIsGuest(false)->save(); $response = $this->graphQlQuery($query, [], '', $this->headers); - $expectedResponse = [ - 'customerDownloadableProducts' => [ - 'items' => [ - [ - 'order_increment_id' => $order->getIncrementId(), - 'date' => '', - 'status' => 'pending', - 'download_url' => 'http://example.com/downloadable.txt', - 'remaining_downloads' => '1' - ] - ] - ] - ]; - - $this->assertEquals($expectedResponse, $response); + $this->assertEquals( + $order->getIncrementId(), + $response['customerDownloadableProducts']['items'][0]['order_increment_id'] + ); } } From bfe26d8ce7a70597c5ad659ef1a551680411b6e2 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Wed, 31 Jul 2019 13:10:49 +0200 Subject: [PATCH 145/841] Removed one file --- .../Resolver/CustomerDownloadableProducts.php | 91 ------------------- 1 file changed, 91 deletions(-) delete mode 100644 app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php diff --git a/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php b/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php deleted file mode 100644 index e3a4f49abb4a8..0000000000000 --- a/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php +++ /dev/null @@ -1,91 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\DownloadableGraphQl\Model\Resolver; - -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Catalog\Model\Product; -use Magento\Downloadable\Helper\Data as DownloadableHelper; -use Magento\Downloadable\Model\Product\Type as Downloadable; -use Magento\Downloadable\Model\ResourceModel\Link\Collection as LinkCollection; -use Magento\Downloadable\Model\ResourceModel\Sample\Collection as SampleCollection; -use Magento\Framework\Data\Collection; -use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Query\EnumLookup; -use Magento\Framework\GraphQl\Query\ResolverInterface; - -/** - * @inheritdoc - * - * Format for downloadable product types - */ -class CustomerDownloadableProducts implements ResolverInterface -{ - /** - * @var EnumLookup - */ - private $enumLookup; - - /** - * @var DownloadableHelper - */ - private $downloadableHelper; - - /** - * @var SampleCollection - */ - private $sampleCollection; - - /** - * @var LinkCollection - */ - private $linkCollection; - - /** - * @param EnumLookup $enumLookup - * @param DownloadableHelper $downloadableHelper - * @param SampleCollection $sampleCollection - * @param LinkCollection $linkCollection - */ - public function __construct( - EnumLookup $enumLookup, - DownloadableHelper $downloadableHelper, - SampleCollection $sampleCollection, - LinkCollection $linkCollection - ) { - $this->enumLookup = $enumLookup; - $this->downloadableHelper = $downloadableHelper; - $this->sampleCollection = $sampleCollection; - $this->linkCollection = $linkCollection; - } - - /** - * @inheritdoc - * - * Add downloadable options to configurable types - * - * @param \Magento\Framework\GraphQl\Config\Element\Field $field - * @param ContextInterface $context - * @param ResolveInfo $info - * @param array|null $value - * @param array|null $args - * @throws \Exception - * @return null|array - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) { - die('aaaa'); - return [1]; - } -} From 845eb5b80d62882a6d2c8fe85ffee6731169c31e Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Wed, 31 Jul 2019 17:22:18 +0300 Subject: [PATCH 146/841] MC-15341: Default product numbers to display results in poor display on Desktop - Updated automated Test script --- .../Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml index 5718844fb6df3..e5f05f1ea00c1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml @@ -10,6 +10,7 @@ <test name="CheckDefaultNumbersProductsToDisplayTest"> <annotations> <features value="Catalog"/> + <stories value="Default product numbers to display in grid"/> <title value="Check default numbers: products to display"/> <description value="Check default numbers: products to display"/> <severity value="MAJOR"/> From 6746a545ae99f93a2a2d4a0097ab9ea3493a13ab Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Wed, 31 Jul 2019 17:27:05 +0300 Subject: [PATCH 147/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" - Update mftf --- .../Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml index a5a0e7f27afd4..2440b916fc018 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml @@ -11,6 +11,7 @@ <test name="AdminOnePayPalSolutionsEnabledAtTheSameTimeTest"> <annotations> <features value="Paypal"/> + <stories value="Payment methods configuration"/> <title value="Only one PayPal solution enabled at the same time"/> <description value="Verify that only one PayPal solution can be enabled"/> <severity value="MAJOR"/> From efb7c618a35cb59b392a09572595c26d73488521 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 1 Aug 2019 12:11:44 -0500 Subject: [PATCH 148/841] MC-18833: Tier price API doesn't take into account indexers mode --- .../Catalog/Model/Indexer/Product/Price.php | 60 ++--- .../Model/Product/Price/TierPriceStorage.php | 133 ++++------ .../Product/Price/TierPriceStorageTest.php | 231 +++++++++--------- .../Indexer/Model/Indexer/CacheCleaner.php | 100 ++++++++ app/code/Magento/Indexer/etc/di.xml | 3 + 5 files changed, 294 insertions(+), 233 deletions(-) create mode 100644 app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price.php index c9936f7e6c691..b703ba82a4052 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price.php @@ -5,43 +5,56 @@ */ namespace Magento\Catalog\Model\Indexer\Product; +use Magento\Catalog\Model\Category as CategoryModel; +use Magento\Catalog\Model\Indexer\Product\Price\Action\Full as FullAction; +use Magento\Catalog\Model\Indexer\Product\Price\Action\Row as RowAction; +use Magento\Catalog\Model\Indexer\Product\Price\Action\Rows as RowsAction; +use Magento\Catalog\Model\Product as ProductModel; +use Magento\Framework\Indexer\ActionInterface as IndexerActionInterface; use Magento\Framework\Indexer\CacheContext; +use Magento\Framework\Mview\ActionInterface as MviewActionInterface; -class Price implements \Magento\Framework\Indexer\ActionInterface, \Magento\Framework\Mview\ActionInterface +/** + * Price indexer + */ +class Price implements IndexerActionInterface, MviewActionInterface { /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\Action\Row + * @var RowAction */ protected $_productPriceIndexerRow; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\Action\Rows + * @var RowsAction */ protected $_productPriceIndexerRows; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\Action\Full + * @var FullAction */ protected $_productPriceIndexerFull; /** - * @var \Magento\Framework\Indexer\CacheContext + * @var CacheContext */ private $cacheContext; /** - * @param Price\Action\Row $productPriceIndexerRow - * @param Price\Action\Rows $productPriceIndexerRows - * @param Price\Action\Full $productPriceIndexerFull + * @param RowAction $productPriceIndexerRow + * @param RowsAction $productPriceIndexerRows + * @param FullAction $productPriceIndexerFull + * @param CacheContext $cacheContext */ public function __construct( - \Magento\Catalog\Model\Indexer\Product\Price\Action\Row $productPriceIndexerRow, - \Magento\Catalog\Model\Indexer\Product\Price\Action\Rows $productPriceIndexerRows, - \Magento\Catalog\Model\Indexer\Product\Price\Action\Full $productPriceIndexerFull + RowAction $productPriceIndexerRow, + RowsAction $productPriceIndexerRows, + FullAction $productPriceIndexerFull, + CacheContext $cacheContext ) { $this->_productPriceIndexerRow = $productPriceIndexerRow; $this->_productPriceIndexerRows = $productPriceIndexerRows; $this->_productPriceIndexerFull = $productPriceIndexerFull; + $this->cacheContext = $cacheContext; } /** @@ -53,7 +66,7 @@ public function __construct( public function execute($ids) { $this->_productPriceIndexerRows->execute($ids); - $this->getCacheContext()->registerEntities(\Magento\Catalog\Model\Product::CACHE_TAG, $ids); + $this->cacheContext->registerEntities(ProductModel::CACHE_TAG, $ids); } /** @@ -64,10 +77,10 @@ public function execute($ids) public function executeFull() { $this->_productPriceIndexerFull->execute(); - $this->getCacheContext()->registerTags( + $this->cacheContext->registerTags( [ - \Magento\Catalog\Model\Category::CACHE_TAG, - \Magento\Catalog\Model\Product::CACHE_TAG + CategoryModel::CACHE_TAG, + ProductModel::CACHE_TAG ] ); } @@ -81,6 +94,7 @@ public function executeFull() public function executeList(array $ids) { $this->_productPriceIndexerRows->execute($ids); + $this->cacheContext->registerEntities(ProductModel::CACHE_TAG, $ids); } /** @@ -92,20 +106,6 @@ public function executeList(array $ids) public function executeRow($id) { $this->_productPriceIndexerRow->execute($id); - } - - /** - * Get cache context - * - * @return \Magento\Framework\Indexer\CacheContext - * @deprecated 100.0.11 - */ - protected function getCacheContext() - { - if (!($this->cacheContext instanceof CacheContext)) { - return \Magento\Framework\App\ObjectManager::getInstance()->get(CacheContext::class); - } else { - return $this->cacheContext; - } + $this->cacheContext->registerEntities(ProductModel::CACHE_TAG, [$id]); } } diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php index 3ee064670a460..6efff27fa2287 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -7,11 +7,15 @@ namespace Magento\Catalog\Model\Product\Price; use Magento\Catalog\Api\Data\TierPriceInterface; +use Magento\Catalog\Api\TierPriceStorageInterface; +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor; +use Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator; +use Magento\Catalog\Model\ProductIdLocatorInterface; /** * Tier price storage. */ -class TierPriceStorage implements \Magento\Catalog\Api\TierPriceStorageInterface +class TierPriceStorage implements TierPriceStorageInterface { /** * Tier price resource model. @@ -23,7 +27,7 @@ class TierPriceStorage implements \Magento\Catalog\Api\TierPriceStorageInterface /** * Tier price validator. * - * @var \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator + * @var TierPriceValidator */ private $tierPriceValidator; @@ -35,65 +39,38 @@ class TierPriceStorage implements \Magento\Catalog\Api\TierPriceStorageInterface private $tierPriceFactory; /** - * Price indexer. + * Price index processor. * - * @var \Magento\Catalog\Model\Indexer\Product\Price + * @var PriceIndexerProcessor */ - private $priceIndexer; + private $priceIndexProcessor; /** * Product ID locator. * - * @var \Magento\Catalog\Model\ProductIdLocatorInterface + * @var ProductIdLocatorInterface */ private $productIdLocator; - /** - * Page cache config. - * - * @var \Magento\PageCache\Model\Config - */ - private $config; - - /** - * Cache type list. - * - * @var \Magento\Framework\App\Cache\TypeListInterface - */ - private $typeList; - - /** - * Indexer chunk value. - * - * @var int - */ - private $indexerChunkValue = 500; - /** * @param TierPricePersistence $tierPricePersistence - * @param \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator $tierPriceValidator + * @param TierPriceValidator $tierPriceValidator * @param TierPriceFactory $tierPriceFactory - * @param \Magento\Catalog\Model\Indexer\Product\Price $priceIndexer - * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator - * @param \Magento\PageCache\Model\Config $config - * @param \Magento\Framework\App\Cache\TypeListInterface $typeList + * @param PriceIndexerProcessor $priceIndexProcessor + * @param ProductIdLocatorInterface $productIdLocator */ public function __construct( TierPricePersistence $tierPricePersistence, - \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator $tierPriceValidator, + TierPriceValidator $tierPriceValidator, TierPriceFactory $tierPriceFactory, - \Magento\Catalog\Model\Indexer\Product\Price $priceIndexer, - \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator, - \Magento\PageCache\Model\Config $config, - \Magento\Framework\App\Cache\TypeListInterface $typeList + PriceIndexerProcessor $priceIndexProcessor, + ProductIdLocatorInterface $productIdLocator ) { $this->tierPricePersistence = $tierPricePersistence; $this->tierPriceValidator = $tierPriceValidator; $this->tierPriceFactory = $tierPriceFactory; - $this->priceIndexer = $priceIndexer; + $this->priceIndexProcessor = $priceIndexProcessor; $this->productIdLocator = $productIdLocator; - $this->config = $config; - $this->typeList = $typeList; } /** @@ -102,8 +79,10 @@ public function __construct( public function get(array $skus) { $skus = $this->tierPriceValidator->validateSkus($skus); + $skuByIdLookup = $this->buildSkuByIdLookup($skus); + $prices = $this->getExistingPrices($skuByIdLookup); - return $this->getExistingPrices($skus); + return $prices; } /** @@ -111,18 +90,21 @@ public function get(array $skus) */ public function update(array $prices) { - $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices); $skus = array_unique( - array_map(function ($price) { - return $price->getSku(); - }, $prices) + array_map( + function (TierPriceInterface $price) { + return $price->getSku(); + }, + $prices + ) ); - $result = $this->tierPriceValidator->retrieveValidationResult($prices, $this->getExistingPrices($skus, true)); + $skuByIdLookup = $this->buildSkuByIdLookup($skus); + $existingPrices = $this->getExistingPrices($skuByIdLookup, true); + $result = $this->tierPriceValidator->retrieveValidationResult($prices, $existingPrices); $prices = $this->removeIncorrectPrices($prices, $result->getFailedRowIds()); $formattedPrices = $this->retrieveFormattedPrices($prices); $this->tierPricePersistence->update($formattedPrices); - $this->reindexPrices($affectedIds); - $this->invalidateFullPageCache(); + $this->reindexPrices(array_keys($skuByIdLookup)); return $result->getFailedItems(); } @@ -138,7 +120,6 @@ public function replace(array $prices) $formattedPrices = $this->retrieveFormattedPrices($prices); $this->tierPricePersistence->replace($formattedPrices, $affectedIds); $this->reindexPrices($affectedIds); - $this->invalidateFullPageCache(); return $result->getFailedItems(); } @@ -154,7 +135,6 @@ public function delete(array $prices) $priceIds = $this->retrieveAffectedPriceIds($prices); $this->tierPricePersistence->delete($priceIds); $this->reindexPrices($affectedIds); - $this->invalidateFullPageCache(); return $result->getFailedItems(); } @@ -162,18 +142,16 @@ public function delete(array $prices) /** * Get existing prices by SKUs. * - * @param array $skus + * @param array $skuByIdLookup * @param bool $groupBySku [optional] * @return array */ - private function getExistingPrices(array $skus, $groupBySku = false) + private function getExistingPrices(array $skuByIdLookup, bool $groupBySku = false): array { - $ids = $this->retrieveAffectedIds($skus); - $rawPrices = $this->tierPricePersistence->get($ids); + $rawPrices = $this->tierPricePersistence->get(array_keys($skuByIdLookup)); $prices = []; if ($rawPrices) { $linkField = $this->tierPricePersistence->getEntityLinkField(); - $skuByIdLookup = $this->buildSkuByIdLookup($skus); foreach ($rawPrices as $rawPrice) { $sku = $skuByIdLookup[$rawPrice[$linkField]]; $price = $this->tierPriceFactory->create($rawPrice, $sku); @@ -194,7 +172,7 @@ private function getExistingPrices(array $skus, $groupBySku = false) * @param array $prices * @return array */ - private function retrieveFormattedPrices(array $prices) + private function retrieveFormattedPrices(array $prices): array { $formattedPrices = []; @@ -215,12 +193,15 @@ private function retrieveFormattedPrices(array $prices) * @param TierPriceInterface[] $prices * @return array */ - private function retrieveAffectedProductIdsForPrices(array $prices) + private function retrieveAffectedProductIdsForPrices(array $prices): array { $skus = array_unique( - array_map(function ($price) { - return $price->getSku(); - }, $prices) + array_map( + function (TierPriceInterface $price) { + return $price->getSku(); + }, + $prices + ) ); return $this->retrieveAffectedIds($skus); @@ -232,7 +213,7 @@ private function retrieveAffectedProductIdsForPrices(array $prices) * @param array $skus * @return array */ - private function retrieveAffectedIds(array $skus) + private function retrieveAffectedIds(array $skus): array { $affectedIds = []; @@ -249,7 +230,7 @@ private function retrieveAffectedIds(array $skus) * @param array $prices * @return array */ - private function retrieveAffectedPriceIds(array $prices) + private function retrieveAffectedPriceIds(array $prices): array { $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices); $formattedPrices = $this->retrieveFormattedPrices($prices); @@ -270,7 +251,7 @@ private function retrieveAffectedPriceIds(array $prices) * @param array $existingPrices * @return int|null */ - private function retrievePriceId(array $price, array $existingPrices) + private function retrievePriceId(array $price, array $existingPrices): ?int { $linkField = $this->tierPricePersistence->getEntityLinkField(); @@ -281,7 +262,7 @@ private function retrievePriceId(array $price, array $existingPrices) && $this->isCorrectPriceValue($existingPrice, $price) && $existingPrice[$linkField] == $price[$linkField] ) { - return $existingPrice['value_id']; + return (int) $existingPrice['value_id']; } } @@ -295,7 +276,7 @@ private function retrievePriceId(array $price, array $existingPrices) * @param array $price * @return bool */ - private function isCorrectPriceValue(array $existingPrice, array $price) + private function isCorrectPriceValue(array $existingPrice, array $price): bool { return ($existingPrice['value'] != 0 && $existingPrice['value'] == $price['value']) || ($existingPrice['percentage_value'] !== null @@ -308,7 +289,7 @@ private function isCorrectPriceValue(array $existingPrice, array $price) * @param array $skus * @return array */ - private function buildSkuByIdLookup($skus) + private function buildSkuByIdLookup(array $skus): array { $lookup = []; foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $sku => $ids) { @@ -320,28 +301,16 @@ private function buildSkuByIdLookup($skus) return $lookup; } - /** - * Invalidate full page cache. - * - * @return void - */ - private function invalidateFullPageCache() - { - if ($this->config->isEnabled()) { - $this->typeList->invalidate('full_page'); - } - } - /** * Reindex prices. * * @param array $ids * @return void */ - private function reindexPrices(array $ids) + private function reindexPrices(array $ids): void { - foreach (array_chunk($ids, $this->indexerChunkValue) as $affectedIds) { - $this->priceIndexer->execute($affectedIds); + if (!empty($ids)) { + $this->priceIndexProcessor->reindexList($ids); } } @@ -352,7 +321,7 @@ private function reindexPrices(array $ids) * @param array $ids * @return array */ - private function removeIncorrectPrices(array $prices, array $ids) + private function removeIncorrectPrices(array $prices, array $ids): array { foreach ($ids as $id) { unset($prices[$id]); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php index a97f2281125a6..34f43b725da57 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php @@ -6,46 +6,44 @@ namespace Magento\Catalog\Test\Unit\Model\Product\Price; +use Magento\Catalog\Api\Data\TierPriceInterface; +use Magento\Catalog\Model\Indexer\Product\Price\Processor as PriceIndexerProcessor; +use Magento\Catalog\Model\Product\Price\TierPriceFactory; +use Magento\Catalog\Model\Product\Price\TierPricePersistence; +use Magento\Catalog\Model\Product\Price\Validation\Result as PriceValidationResult; +use Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator; +use Magento\Catalog\Model\ProductIdLocatorInterface; + /** * TierPriceStorage test. */ class TierPriceStorageTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Catalog\Model\Product\Price\TierPricePersistence|\PHPUnit_Framework_MockObject_MockObject + * @var TierPricePersistence|\PHPUnit_Framework_MockObject_MockObject */ private $tierPricePersistence; /** - * @var \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator|\PHPUnit_Framework_MockObject_MockObject + * @var TierPriceValidator|\PHPUnit_Framework_MockObject_MockObject */ private $tierPriceValidator; /** - * @var \Magento\Catalog\Model\Product\Price\TierPriceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var TierPriceFactory|\PHPUnit_Framework_MockObject_MockObject */ private $tierPriceFactory; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price|\PHPUnit_Framework_MockObject_MockObject + * @var PriceIndexerProcessor|\PHPUnit_Framework_MockObject_MockObject */ - private $priceIndexer; + private $priceIndexProcessor; /** - * @var \Magento\Catalog\Model\ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ProductIdLocatorInterface|\PHPUnit_Framework_MockObject_MockObject */ private $productIdLocator; - /** - * @var \Magento\PageCache\Model\Config|\PHPUnit_Framework_MockObject_MockObject - */ - private $config; - - /** - * @var \Magento\Framework\App\Cache\TypeListInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $typeList; - /** * @var \Magento\Catalog\Model\Product\Price\TierPriceStorage */ @@ -56,36 +54,13 @@ class TierPriceStorageTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->tierPricePersistence = $this->getMockBuilder( - \Magento\Catalog\Model\Product\Price\TierPricePersistence::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->tierPricePersistence->expects($this->any()) - ->method('getEntityLinkField') - ->willReturn('row_id'); - $this->tierPriceValidator = $this->getMockBuilder( - \Magento\Catalog\Model\Product\Price\Validation\TierPriceValidator::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->tierPriceFactory = $this->getMockBuilder( - \Magento\Catalog\Model\Product\Price\TierPriceFactory::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->priceIndexer = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Price::class) - ->disableOriginalConstructor() - ->getMock(); - $this->productIdLocator = $this->getMockBuilder(\Magento\Catalog\Model\ProductIdLocatorInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->config = $this->getMockBuilder(\Magento\PageCache\Model\Config::class) - ->disableOriginalConstructor() - ->getMock(); - $this->typeList = $this->getMockBuilder(\Magento\Framework\App\Cache\TypeListInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); + $this->tierPricePersistence = $this->createMock(TierPricePersistence::class); + $this->tierPricePersistence->method('getEntityLinkField') + ->willReturn('entity_id'); + $this->tierPriceValidator = $this->createMock(TierPriceValidator::class); + $this->tierPriceFactory = $this->createMock(TierPriceFactory::class); + $this->priceIndexProcessor = $this->createMock(PriceIndexerProcessor::class); + $this->productIdLocator = $this->createMock(ProductIdLocatorInterface::class); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->tierPriceStorage = $objectManager->getObject( @@ -94,10 +69,8 @@ protected function setUp() 'tierPricePersistence' => $this->tierPricePersistence, 'tierPriceValidator' => $this->tierPriceValidator, 'tierPriceFactory' => $this->tierPriceFactory, - 'priceIndexer' => $this->priceIndexer, + 'priceIndexProcessor' => $this->priceIndexProcessor, 'productIdLocator' => $this->productIdLocator, - 'config' => $this->config, - 'typeList' => $this->typeList, ] ); } @@ -125,7 +98,7 @@ public function testGet() [ [ 'value_id' => 1, - 'row_id' => 2, + 'entity_id' => 2, 'all_groups' => 1, 'customer_group_id' => 0, 'qty' => 2.0000, @@ -135,7 +108,7 @@ public function testGet() ], [ 'value_id' => 2, - 'row_id' => 3, + 'entity_id' => 3, 'all_groups' => 1, 'customer_group_id' => 0, 'qty' => 3.0000, @@ -145,7 +118,7 @@ public function testGet() ] ] ); - $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); + $price = $this->getMockBuilder(TierPriceInterface::class)->getMockForAbstractClass(); $this->tierPriceFactory->expects($this->atLeastOnce())->method('create')->willReturn($price); $prices = $this->tierPriceStorage->get($skus); $this->assertNotEmpty($prices); @@ -183,36 +156,37 @@ public function testGetWithoutTierPrices() */ public function testUpdate() { - $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); - $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) - ->disableOriginalConstructor() - ->getMock(); - $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]); + $price = $this->createMock(TierPriceInterface::class); + $result = $this->createMock(PriceValidationResult::class); + $result->expects($this->once()) + ->method('getFailedRowIds') + ->willReturn([]); $this->productIdLocator->expects($this->atLeastOnce()) ->method('retrieveProductIdsBySkus') ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]); - $this->tierPriceValidator - ->expects($this->atLeastOnce()) + $this->tierPriceValidator->expects($this->once()) ->method('retrieveValidationResult') ->willReturn($result); - $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn( - [ - 'row_id' => 2, - 'all_groups' => 1, - 'customer_group_id' => 0, - 'qty' => 2, - 'value' => 3, - 'percentage_value' => null, - 'website_id' => 0 - ] - ); + $this->tierPriceFactory->expects($this->once()) + ->method('createSkeleton') + ->willReturn( + [ + 'entity_id' => 2, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 2, + 'value' => 3, + 'percentage_value' => null, + 'website_id' => 0 + ] + ); $this->tierPricePersistence->expects($this->once()) ->method('get') ->willReturn( [ [ 'value_id' => 1, - 'row_id' => 2, + 'entity_id' => 2, 'all_groups' => 1, 'customer_group_id' => 0, 'qty' => 2.0000, @@ -222,11 +196,15 @@ public function testUpdate() ] ] ); - $this->tierPricePersistence->expects($this->atLeastOnce())->method('update'); - $this->priceIndexer->expects($this->atLeastOnce())->method('execute'); - $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); - $this->typeList->expects($this->atLeastOnce())->method('invalidate'); - $price->expects($this->atLeastOnce())->method('getSku')->willReturn('simple'); + $this->tierPricePersistence->expects($this->once()) + ->method('update'); + $this->priceIndexProcessor->expects($this->once()) + ->method('reindexList') + ->with([2, 3]); + $price->expects($this->atLeastOnce()) + ->method('getSku') + ->willReturn('simple'); + $this->assertEmpty($this->tierPriceStorage->update([$price])); } @@ -237,35 +215,41 @@ public function testUpdate() */ public function testReplace() { - $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); - $price->expects($this->atLeastOnce())->method('getSku')->willReturn('virtual'); - $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) - ->disableOriginalConstructor() - ->getMock(); - $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]); + $price = $this->createMock(TierPriceInterface::class); + $price->expects($this->atLeastOnce()) + ->method('getSku') + ->willReturn('virtual'); + $result = $this->createMock(PriceValidationResult::class); + $result->expects($this->once()) + ->method('getFailedRowIds') + ->willReturn([]); $this->productIdLocator->expects($this->atLeastOnce()) ->method('retrieveProductIdsBySkus') ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]); $this->tierPriceValidator - ->expects($this->atLeastOnce()) + ->expects($this->once()) ->method('retrieveValidationResult') ->willReturn($result); - $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn( - [ - 'row_id' => 3, - 'all_groups' => 1, - 'customer_group_id' => 0, - 'qty' => 3, - 'value' => 7, - 'percentage_value' => null, - 'website_id' => 0 - ] - ); - $this->tierPricePersistence->expects($this->atLeastOnce())->method('replace'); - $this->priceIndexer->expects($this->atLeastOnce())->method('execute'); - $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); - $this->typeList->expects($this->atLeastOnce())->method('invalidate'); + $this->tierPriceFactory->expects($this->once()) + ->method('createSkeleton') + ->willReturn( + [ + 'entity_id' => 3, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 3, + 'value' => 7, + 'percentage_value' => null, + 'website_id' => 0 + ] + ); + $this->tierPricePersistence->expects($this->once()) + ->method('replace'); + $this->priceIndexProcessor->expects($this->once()) + ->method('reindexList') + ->with([2, 3]); + $this->assertEmpty($this->tierPriceStorage->replace([$price])); } @@ -276,13 +260,15 @@ public function testReplace() */ public function testDelete() { - $price = $this->getMockBuilder(\Magento\Catalog\Api\Data\TierPriceInterface::class)->getMockForAbstractClass(); - $price->expects($this->atLeastOnce())->method('getSku')->willReturn('simple'); - $result = $this->getMockBuilder(\Magento\Catalog\Model\Product\Price\Validation\Result::class) - ->disableOriginalConstructor() - ->getMock(); - $result->expects($this->atLeastOnce())->method('getFailedRowIds')->willReturn([]); - $this->tierPriceValidator->expects($this->atLeastOnce()) + $price = $this->createMock(TierPriceInterface::class); + $price->expects($this->atLeastOnce()) + ->method('getSku') + ->willReturn('simple'); + $result = $this->createMock(PriceValidationResult::class); + $result->expects($this->once()) + ->method('getFailedRowIds') + ->willReturn([]); + $this->tierPriceValidator->expects($this->once()) ->method('retrieveValidationResult') ->willReturn($result); $this->productIdLocator->expects($this->atLeastOnce()) @@ -294,7 +280,7 @@ public function testDelete() [ [ 'value_id' => 7, - 'row_id' => 7, + 'entity_id' => 7, 'all_groups' => 1, 'customer_group_id' => 0, 'qty' => 5.0000, @@ -304,21 +290,24 @@ public function testDelete() ] ] ); - $this->tierPriceFactory->expects($this->atLeastOnce())->method('createSkeleton')->willReturn( - [ - 'row_id' => 3, - 'all_groups' => 1, - 'customer_group_id' => 0, - 'qty' => 3, - 'value' => 7, - 'percentage_value' => null, - 'website_id' => 0 - ] - ); - $this->tierPricePersistence->expects($this->atLeastOnce())->method('delete'); - $this->priceIndexer->expects($this->atLeastOnce())->method('execute'); - $this->config->expects($this->atLeastOnce())->method('isEnabled')->willReturn(true); - $this->typeList->expects($this->atLeastOnce())->method('invalidate'); + $this->tierPriceFactory->expects($this->once()) + ->method('createSkeleton')->willReturn( + [ + 'entity_id' => 3, + 'all_groups' => 1, + 'customer_group_id' => 0, + 'qty' => 3, + 'value' => 7, + 'percentage_value' => null, + 'website_id' => 0 + ] + ); + $this->tierPricePersistence->expects($this->once()) + ->method('delete'); + $this->priceIndexProcessor->expects($this->once()) + ->method('reindexList') + ->with([2]); + $this->assertEmpty($this->tierPriceStorage->delete([$price])); } } diff --git a/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php new file mode 100644 index 0000000000000..c75a3541ba9c3 --- /dev/null +++ b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model\Indexer; + +use Magento\Framework\App\CacheInterface; +use Magento\Framework\Event\Manager as EventManager; +use Magento\Framework\Indexer\ActionInterface; +use Magento\Framework\Indexer\CacheContext; + +/** + * Clean cache for reindexed entities after executed action. + */ +class CacheCleaner +{ + /** + * @var EventManager + */ + private $eventManager; + + /** + * @var CacheContext + */ + private $cacheContext; + + /** + * @var CacheInterface + */ + private $appCache; + + /** + * @param EventManager $eventManager + * @param CacheContext $cacheContext + * @param CacheInterface $appCache + */ + public function __construct( + EventManager $eventManager, + CacheContext $cacheContext, + CacheInterface $appCache + ) { + $this->eventManager = $eventManager; + $this->cacheContext = $cacheContext; + $this->appCache = $appCache; + } + + /** + * Clean cache after full reindex. + * + * @param ActionInterface $subject + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecuteFull(ActionInterface $subject) + { + $this->cleanCache(); + } + + /** + * Clean cache after reindexed list. + * + * @param ActionInterface $subject + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecuteList(ActionInterface $subject) + { + $this->cleanCache(); + } + + /** + * Clean cache after reindexed row. + * + * @param ActionInterface $subject + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecuteRow(ActionInterface $subject) + { + $this->cleanCache(); + } + + /** + * Clean cache. + * + * @return void + */ + private function cleanCache() + { + $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); + + $identities = $this->cacheContext->getIdentities(); + if (!empty($identities)) { + $this->appCache->clean($identities); + } + } +} diff --git a/app/code/Magento/Indexer/etc/di.xml b/app/code/Magento/Indexer/etc/di.xml index 76e7e7a46224b..9496f29cb1d87 100644 --- a/app/code/Magento/Indexer/etc/di.xml +++ b/app/code/Magento/Indexer/etc/di.xml @@ -67,4 +67,7 @@ <argument name="indexer" xsi:type="object" shared="false">Magento\Indexer\Model\Indexer</argument> </arguments> </type> + <type name="Magento\Framework\Indexer\ActionInterface"> + <plugin name="cache_cleaner_after_reindex" type="Magento\Indexer\Model\Indexer\CacheCleaner" /> + </type> </config> From a4f09b45dbaa3e42420922ef6befffeff9bdccb1 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 1 Aug 2019 13:56:01 -0500 Subject: [PATCH 149/841] MC-18833: Tier price API doesn't take into account indexers mode --- .../Magento/Catalog/Model/Product/Price/TierPriceStorage.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php index 6efff27fa2287..0c371a48fde96 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -218,10 +218,10 @@ private function retrieveAffectedIds(array $skus): array $affectedIds = []; foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productId) { - $affectedIds = array_merge($affectedIds, array_keys($productId)); + $affectedIds[] = array_keys($productId); } - return array_unique($affectedIds); + return array_unique(array_merge(...$affectedIds)); } /** From 814c68a48d433ff8f73c63352618b8dcb0e38c33 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Fri, 2 Aug 2019 02:49:10 -0500 Subject: [PATCH 150/841] MC-18833: Tier price API doesn't take into account indexers mode --- .../Magento/Catalog/Model/Product/Price/TierPriceStorage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php index 0c371a48fde96..b9e1a27e46e41 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -221,7 +221,7 @@ private function retrieveAffectedIds(array $skus): array $affectedIds[] = array_keys($productId); } - return array_unique(array_merge(...$affectedIds)); + return $affectedIds ? array_unique(array_merge(...$affectedIds)) : []; } /** From a7792ed1bfd7cc752ab99553e3ab80e2538fc754 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Fri, 2 Aug 2019 13:01:37 +0300 Subject: [PATCH 151/841] MC-18710: Prices for "All store views" scope overridden in product's custom option in accordance with last price update for any other scope. --- .../Model/Import/Product/Option.php | 84 +++++++++++++------ .../Model/Import/Product/Type/OptionTest.php | 4 +- ...ustom_options_and_multiple_store_views.csv | 4 +- 3 files changed, 61 insertions(+), 31 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index 6cdafa7fc6f5a..efd763ecfcf8e 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -684,7 +684,12 @@ protected function _getNewOptionsWithTheSameTitlesErrorRows(array $sourceProduct ksort($outerTitles); ksort($innerTitles); if ($outerTitles === $innerTitles) { - $errorRows = array_merge($errorRows, $innerData['rows'], $outerData['rows']); + foreach ($innerData['rows'] as $innerDataRow) { + $errorRows[] = $innerDataRow; + } + foreach ($outerData['rows'] as $outerDataRow) { + $errorRows[] = $outerDataRow; + } } } } @@ -719,7 +724,9 @@ protected function _findOldOptionsWithTheSameTitles() } } if ($optionsCount > 1) { - $errorRows = array_merge($errorRows, $outerData['rows']); + foreach ($outerData['rows'] as $dataRow) { + $errorRows[] = $dataRow; + } } } } @@ -747,7 +754,9 @@ protected function _findNewOldOptionsTypeMismatch() ksort($outerTitles); ksort($innerTitles); if ($outerTitles === $innerTitles && $outerData['type'] != $innerData['type']) { - $errorRows = array_merge($errorRows, $outerData['rows']); + foreach ($outerData['rows'] as $dataRow) { + $errorRows[] = $dataRow; + } } } } @@ -959,8 +968,10 @@ public function validateRow(array $rowData, $rowNumber) $multiRowData = $this->_getMultiRowFormat($rowData); - foreach ($multiRowData as $optionData) { - $combinedData = array_merge($rowData, $optionData); + foreach ($multiRowData as $combinedData) { + foreach ($rowData as $key => $field) { + $combinedData[$key] = $field; + } if ($this->_isRowWithCustomOption($combinedData)) { if ($this->_isMainOptionRow($combinedData)) { @@ -1109,15 +1120,15 @@ protected function _getMultiRowFormat($rowData) foreach ($rowData['custom_options'] as $name => $customOption) { $i++; foreach ($customOption as $rowOrder => $optionRow) { - $row = array_merge( - [ - self::COLUMN_STORE => '', - self::COLUMN_TITLE => $name, - self::COLUMN_SORT_ORDER => $i, - self::COLUMN_ROW_SORT => $rowOrder - ], - $this->processOptionRow($name, $optionRow) - ); + $row = [ + self::COLUMN_STORE => '', + self::COLUMN_TITLE => $name, + self::COLUMN_SORT_ORDER => $i, + self::COLUMN_ROW_SORT => $rowOrder + ]; + foreach ($this->processOptionRow($name, $optionRow) as $key => $value) { + $row[$key] = $value; + } $name = ''; $multiRow[] = $row; } @@ -1215,6 +1226,8 @@ private function addFileOptions($result, $optionRow) * * @return boolean * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.NPathComplexity) */ protected function _importData() { @@ -1256,9 +1269,11 @@ protected function _importData() $optionsToRemove[] = $this->_rowProductId; } } - foreach ($multiRowData as $optionData) { - $combinedData = array_merge($rowData, $optionData); + foreach ($multiRowData as $combinedData) { + foreach ($rowData as $key => $field) { + $combinedData[$key] = $field; + } if (!$this->isRowAllowedToImport($combinedData, $rowNumber) || !$this->_parseRequiredData($combinedData) ) { @@ -1441,7 +1456,9 @@ protected function _collectOptionMainData( if (!$this->_isRowHasSpecificType($this->_rowType) && ($priceData = $this->_getPriceData($rowData, $nextOptionId, $this->_rowType)) ) { - $prices[$nextOptionId] = $priceData; + if ($this->_isPriceGlobal) { + $prices[$nextOptionId][Store::DEFAULT_STORE_ID] = $priceData; + } } if (!isset($products[$this->_rowProductId])) { @@ -1547,6 +1564,7 @@ protected function _collectOptionTitle(array $rowData, $prevOptionId, array &$ti * @param array &$prices * @param array &$typeValues * @return $this + * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ protected function _compareOptionsWithExisting(array &$options, array &$titles, array &$prices, array &$typeValues) { @@ -1557,7 +1575,9 @@ protected function _compareOptionsWithExisting(array &$options, array &$titles, $titles[$optionId] = $titles[$newOptionId]; unset($titles[$newOptionId]); if (isset($prices[$newOptionId])) { - $prices[$newOptionId]['option_id'] = $optionId; + foreach ($prices[$newOptionId] as $storeId => $priceStoreData) { + $prices[$newOptionId][$storeId]['option_id'] = $optionId; + } } if (isset($typeValues[$newOptionId])) { $typeValues[$optionId] = $typeValues[$newOptionId]; @@ -1590,8 +1610,10 @@ private function restoreOriginalOptionTypeIds(array &$typeValues, array &$typePr $optionType['option_type_id'] = $existingTypeId; $typeTitles[$existingTypeId] = $typeTitles[$optionTypeId]; unset($typeTitles[$optionTypeId]); - $typePrices[$existingTypeId] = $typePrices[$optionTypeId]; - unset($typePrices[$optionTypeId]); + if (isset($typePrices[$optionTypeId])) { + $typePrices[$existingTypeId] = $typePrices[$optionTypeId]; + unset($typePrices[$optionTypeId]); + } // If option type titles match at least in one store, consider current option type as existing break; } @@ -1651,7 +1673,7 @@ protected function _parseRequiredData(array $rowData) if (!isset($this->_storeCodeToId[$rowData[self::COLUMN_STORE]])) { return false; } - $this->_rowStoreId = $this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; + $this->_rowStoreId = (int)$this->_storeCodeToId[$rowData[self::COLUMN_STORE]]; } else { $this->_rowStoreId = Store::DEFAULT_STORE_ID; } @@ -1767,7 +1789,7 @@ protected function _getPriceData(array $rowData, $optionId, $type) ) { $priceData = [ 'option_id' => $optionId, - 'store_id' => Store::DEFAULT_STORE_ID, + 'store_id' => $this->_rowStoreId, 'price_type' => 'fixed', ]; @@ -1901,11 +1923,19 @@ protected function _saveTitles(array $titles) protected function _savePrices(array $prices) { if ($prices) { - $this->_connection->insertOnDuplicate( - $this->_tables['catalog_product_option_price'], - $prices, - ['price', 'price_type'] - ); + $optionPriceRows = []; + foreach ($prices as $storesData) { + foreach ($storesData as $row) { + $optionPriceRows[] = $row; + } + } + if ($optionPriceRows) { + $this->_connection->insertOnDuplicate( + $this->_tables['catalog_product_option_price'], + $optionPriceRows, + ['price', 'price_type'] + ); + } } return $this; diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php index f0a52a67e0095..9f63decac5ff7 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php @@ -79,8 +79,8 @@ class OptionTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractIm * @var array */ protected $_expectedPrices = [ - 2 => ['option_id' => 2, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 0], - 3 => ['option_id' => 3, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 2] + 0 => ['option_id' => 2, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 0], + 1 => ['option_id' => 3, 'store_id' => 0, 'price_type' => 'fixed', 'price' => 2] ]; /** diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv index f068365258546..cfd5b770d35d8 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv @@ -1,4 +1,4 @@ sku,website_code,store_view_code,attribute_set_code,product_type,name,description,short_description,weight,product_online,visibility,product_websites,categories,price,special_price,special_price_from_date,special_price_to_date,tax_class_name,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,additional_images,additional_image_labels,configurable_variation_labels,configurable_variations,bundle_price_type,bundle_sku_type,bundle_weight_type,bundle_values,downloadble_samples,downloadble_links,associated_skus,related_skus,crosssell_skus,upsell_skus,custom_options,additional_attributes,manage_stock,is_in_stock,qty,out_of_stock_qty,is_qty_decimal,allow_backorders,min_cart_qty,max_cart_qty,notify_on_stock_below,qty_increments,enable_qty_increments,is_decimal_divided,new_from_date,new_to_date,gift_message_available,created_at,updated_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_price,msrp_display_actual_price_type,map_enabled -simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 2,sku=3-2-select|name=Test Field Title,type=field,required=1,sku=1-text,price=0,price_type=fixed,max_characters=10|name=Test Date and Time Title,type=date_time,required=1,price=2,sku=2-date|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 1,sku=4-1-select|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 2,sku=4-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 1,sku=5-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 2,sku=5-2-radio",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 2,sku=3-2-select|name=Test Field Title,type=field,required=1,sku=1-text,price=0,price_type=fixed,max_characters=10|name=Test Date and Time Title,type=date_time,required=1,sku=2-date|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 1,sku=4-1-select|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 2,sku=4-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 1,sku=5-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 2,sku=5-2-radio",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, simple,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select_default,type=drop_down,option_title=Select Option 1_default|name=Test Select_default,type=drop_down,option_title=Select Option 2_default|name=Test Field Title_default,type=field|name=Test Date and Time Title_default,type=date_time|name=Test Checkbox_default,type=checkbox,option_title=Checkbox Option 1_default|name=Test Checkbox_default,type=checkbox,option_title=Checkbox Option 2_default|name=Test Radio_default,type=radio,option_title=Radio Option 1_default|name=Test Radio_default,type=radio,option_title=Radio Option 2_default",,,,,,,,,,,,,,,,,,,,,,,,,,, -simple,,fixture_second_store,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select_fixture_second_store,type=drop_down,price=1,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,option_title=Select Option 2_fixture_second_store|name=Test Field Title_fixture_second_store,type=field|name=Test Date and Time Title_fixture_second_store,type=date_time|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, +simple,,fixture_second_store,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select_fixture_second_store,type=drop_down,price=1,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,option_title=Select Option 2_fixture_second_store|name=Test Field Title_fixture_second_store,type=field|name=Test Date and Time Title_fixture_second_store,price=8,type=date_time|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, From 98272e90d737e2bb9dd54ca3e5f81737bf848bd6 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Fri, 2 Aug 2019 15:03:51 -0500 Subject: [PATCH 152/841] MC-18833: Tier price API doesn't take into account indexers mode --- .../Model/Product/Price/TierPriceStorage.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php index b9e1a27e46e41..36ef1826462b0 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php @@ -79,10 +79,8 @@ public function __construct( public function get(array $skus) { $skus = $this->tierPriceValidator->validateSkus($skus); - $skuByIdLookup = $this->buildSkuByIdLookup($skus); - $prices = $this->getExistingPrices($skuByIdLookup); - return $prices; + return $this->getExistingPrices($skus); } /** @@ -90,6 +88,7 @@ public function get(array $skus) */ public function update(array $prices) { + $affectedIds = $this->retrieveAffectedProductIdsForPrices($prices); $skus = array_unique( array_map( function (TierPriceInterface $price) { @@ -98,13 +97,11 @@ function (TierPriceInterface $price) { $prices ) ); - $skuByIdLookup = $this->buildSkuByIdLookup($skus); - $existingPrices = $this->getExistingPrices($skuByIdLookup, true); - $result = $this->tierPriceValidator->retrieveValidationResult($prices, $existingPrices); + $result = $this->tierPriceValidator->retrieveValidationResult($prices, $this->getExistingPrices($skus, true)); $prices = $this->removeIncorrectPrices($prices, $result->getFailedRowIds()); $formattedPrices = $this->retrieveFormattedPrices($prices); $this->tierPricePersistence->update($formattedPrices); - $this->reindexPrices(array_keys($skuByIdLookup)); + $this->reindexPrices($affectedIds); return $result->getFailedItems(); } @@ -142,16 +139,18 @@ public function delete(array $prices) /** * Get existing prices by SKUs. * - * @param array $skuByIdLookup + * @param array $skus * @param bool $groupBySku [optional] * @return array */ - private function getExistingPrices(array $skuByIdLookup, bool $groupBySku = false): array + private function getExistingPrices(array $skus, bool $groupBySku = false): array { - $rawPrices = $this->tierPricePersistence->get(array_keys($skuByIdLookup)); + $ids = $this->retrieveAffectedIds($skus); + $rawPrices = $this->tierPricePersistence->get($ids); $prices = []; if ($rawPrices) { $linkField = $this->tierPricePersistence->getEntityLinkField(); + $skuByIdLookup = $this->buildSkuByIdLookup($skus); foreach ($rawPrices as $rawPrice) { $sku = $skuByIdLookup[$rawPrice[$linkField]]; $price = $this->tierPriceFactory->create($rawPrice, $sku); From a16952c0a6070eb2f2a2454da0cbb3376f01a14e Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Sat, 3 Aug 2019 15:48:23 +0200 Subject: [PATCH 153/841] Customer downloadable products added more tests --- .../CustomerDownloadableProductTest.php | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php index ce0d830a0e9fc..81e0fb81dedbd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php @@ -9,6 +9,7 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; @@ -80,4 +81,66 @@ public function testGetCustomerDownloadableProducts() $response['customerDownloadableProducts']['items'][0]['order_increment_id'] ); } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testGetCustomerDownloadableProductsIfProductsDoNotExist() + { + $query = <<<MUTATION +mutation { + generateCustomerToken( + email: "customer@example.com" + password: "password" + ) { + token + } +} +MUTATION; + $response = $this->graphQlMutation($query); + $token = $response['generateCustomerToken']['token']; + $this->headers = ['Authorization' => 'Bearer ' . $token]; + + $query = <<<QUERY + { + customerDownloadableProducts{ + items{ + order_increment_id + date + status + download_url + remaining_downloads + } + } + +} +QUERY; + + $response = $this->graphQlQuery($query, [], '', $this->headers); + $this->assertEmpty($response['customerDownloadableProducts']['items']); + } + + + public function testGuestCannotAccessDownloadableProducts() + { + $query = <<<QUERY + { + customerDownloadableProducts{ + items{ + order_increment_id + date + status + download_url + remaining_downloads + } + } + +} +QUERY; + + $this->expectException(ResponseContainsErrorsException::class); + $this->expectExceptionMessage('GraphQL response contains errors: The current customer isn\'t authorized'); + $this->graphQlQuery($query); + } } From 9a17c463cf97d6457c94b14dd0e97f2d83d91032 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Sat, 3 Aug 2019 16:16:26 +0200 Subject: [PATCH 154/841] Small cs fix --- .../CustomerDownloadableProductTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php index 81e0fb81dedbd..77de8aa3917fb 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php @@ -121,7 +121,6 @@ public function testGetCustomerDownloadableProductsIfProductsDoNotExist() $this->assertEmpty($response['customerDownloadableProducts']['items']); } - public function testGuestCannotAccessDownloadableProducts() { $query = <<<QUERY From 866a15414c4f0bf2c8a8c6e8dd0a97fd869d05a8 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 5 Aug 2019 16:20:28 +0700 Subject: [PATCH 155/841] No alert when user "Apply empty Coupon Code" in create new order form (issue 24015) --- app/code/Magento/Sales/i18n/en_US.csv | 2 ++ .../view/adminhtml/web/order/create/scripts.js | 16 +++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Sales/i18n/en_US.csv b/app/code/Magento/Sales/i18n/en_US.csv index c5657f3a309f7..d09ee8376b2ed 100644 --- a/app/code/Magento/Sales/i18n/en_US.csv +++ b/app/code/Magento/Sales/i18n/en_US.csv @@ -797,3 +797,5 @@ Created,Created Refunds,Refunds "Allow Zero GrandTotal for Creditmemo","Allow Zero GrandTotal for Creditmemo" "Allow Zero GrandTotal","Allow Zero GrandTotal" +"Please enter a coupon code!","Please enter a coupon code!" + diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js index 5f20325eb686e..afc5b3e5243c7 100644 --- a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js +++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js @@ -571,11 +571,17 @@ define([ }, applyCoupon : function(code){ - this.loadArea(['items', 'shipping_method', 'totals', 'billing_method'], true, {'order[coupon][code]':code, reset_shipping: 0}); - this.orderItemChanged = false; - jQuery('html, body').animate({ - scrollTop: 0 - }); + if (!code) { + alert({ + content: jQuery.mage.__('Please enter a coupon code!') + }); + } else { + this.loadArea(['items', 'shipping_method', 'totals', 'billing_method'], true, {'order[coupon][code]':code, reset_shipping: 0}); + this.orderItemChanged = false; + jQuery('html, body').animate({ + scrollTop: 0 + }); + } }, addProduct : function(id){ From 417dd7756fd27f2b63020426bf5fe9a69eb173e0 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 5 Aug 2019 13:01:30 -0500 Subject: [PATCH 156/841] MC-18833: Tier price API doesn't take into account indexers mode --- app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php index c75a3541ba9c3..25b4ae286e332 100644 --- a/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php +++ b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php @@ -91,10 +91,5 @@ public function afterExecuteRow(ActionInterface $subject) private function cleanCache() { $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); - - $identities = $this->cacheContext->getIdentities(); - if (!empty($identities)) { - $this->appCache->clean($identities); - } } } From 3121dfe97a5d4a18ae0d0ef27a9d32fd62e8da1f Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 5 Aug 2019 13:02:11 -0500 Subject: [PATCH 157/841] MC-18833: Tier price API doesn't take into account indexers mode --- app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php index 25b4ae286e332..4a5ea36a68a61 100644 --- a/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php +++ b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php @@ -90,6 +90,9 @@ public function afterExecuteRow(ActionInterface $subject) */ private function cleanCache() { - $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); + $identities = $this->cacheContext->getIdentities(); + if (!empty($identities)) { + $this->appCache->clean($identities); + } } } From c646296331120e3c20e5c73c702eb6093617eff0 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Tue, 6 Aug 2019 23:06:25 +0000 Subject: [PATCH 158/841] Deleted redundant dependency + set scopeType --- app/code/Magento/Newsletter/Model/Config.php | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/Newsletter/Model/Config.php b/app/code/Magento/Newsletter/Model/Config.php index 0da18b661399d..c6e4ba36591fc 100644 --- a/app/code/Magento/Newsletter/Model/Config.php +++ b/app/code/Magento/Newsletter/Model/Config.php @@ -7,6 +7,8 @@ namespace Magento\Newsletter\Model; +use Magento\Framework\App\Config\ScopeConfigInterface; + /** * Newsletter configuration model */ @@ -18,27 +20,19 @@ class Config const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ protected $scopeConfig; - /** - * @var \Magento\Config\Model\ResourceModel\Config - */ - protected $resourceConfig; - /** * Config constructor. * - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Config\Model\ResourceModel\Config $resourceConfig + * @param ScopeConfigInterface $scopeConfig */ public function __construct( - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Config\Model\ResourceModel\Config $resourceConfig + ScopeConfigInterface $scopeConfig ) { $this->scopeConfig = $scopeConfig; - $this->resourceConfig = $resourceConfig; } /** @@ -46,8 +40,8 @@ public function __construct( * * @return bool */ - public function isActive() + public function isActive($scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { - return $this->scopeConfig->isSetFlag(self::XML_PATH_NEWSLETTER_ACTIVE); + return $this->scopeConfig->isSetFlag(self::XML_PATH_NEWSLETTER_ACTIVE, $scopeType); } } From 35e3d10c9648f10c902dda49680a09b0897050cb Mon Sep 17 00:00:00 2001 From: roettigl <l.roettig@techdivision.com> Date: Wed, 7 Aug 2019 10:14:58 +0200 Subject: [PATCH 159/841] MC-18221: Clean up throw and catch ignores --- .../Plugin/Framework/Amqp/Bulk/Exchange.php | 1 - .../Product/Gallery/GalleryManagement.php | 2 - .../Model/ResourceModel/Eav/Attribute.php | 1 - .../Cms/Model/Wysiwyg/Images/Storage.php | 2 - .../Adminhtml/Export/File/Download.php | 1 - .../Magento/Newsletter/Model/Subscriber.php | 1 - composer.json | 2 +- composer.lock | 579 +++++++----------- .../Framework/App/Config/Initial/Reader.php | 1 - .../Framework/Session/SessionManager.php | 2 - 10 files changed, 218 insertions(+), 374 deletions(-) diff --git a/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php b/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php index c5db1f5300c29..37960a64d3861 100644 --- a/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php +++ b/app/code/Magento/AmqpStore/Plugin/Framework/Amqp/Bulk/Exchange.php @@ -91,7 +91,6 @@ public function beforeEnqueue(SubjectExchange $subject, $topic, array $envelopes if ($headers instanceof AMQPTable) { try { $headers->set('store_id', $storeId); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (AMQPInvalidArgumentException $ea) { $errorMessage = sprintf("Can't set storeId to amqp message. Error %s.", $ea->getMessage()); $this->logger->error($errorMessage); diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php index c993e51c8bc09..9e5cf084c25a1 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php @@ -71,10 +71,8 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry) $product->setMediaGalleryEntries($existingMediaGalleryEntries); try { $product = $this->productRepository->save($product); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (InputException $inputException) { throw $inputException; - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { throw new StateException(__("The product can't be saved.")); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index e7c98b218f5ad..23f612582f42e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -193,7 +193,6 @@ public function beforeSave() if ($this->_data[self::KEY_IS_GLOBAL] != $this->_origData[self::KEY_IS_GLOBAL]) { try { $this->attrLockValidator->validate($this); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $exception) { throw new \Magento\Framework\Exception\LocalizedException( __('Do not change the scope. %1', $exception->getMessage()) diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index 953b4d455f52a..e02d2b461a94e 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -447,7 +447,6 @@ public function createDirectory($name, $path) 'id' => $this->_cmsWysiwygImages->convertPathToId($newPath), ]; return $result; - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\FileSystemException $e) { throw new \Magento\Framework\Exception\LocalizedException(__('We cannot create a new directory.')); } @@ -474,7 +473,6 @@ public function deleteDirectory($path) $this->_deleteByPath($path); $path = $this->getThumbnailRoot() . $this->_getRelativePathToRoot($path); $this->_deleteByPath($path); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\FileSystemException $e) { throw new \Magento\Framework\Exception\LocalizedException( __('We cannot delete directory %1.', $this->_getRelativePathToRoot($path)) diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php index 0e6bca26d2062..8dbd9a0ae44ba 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php @@ -72,7 +72,6 @@ public function execute() DirectoryList::VAR_DIR ); } - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (LocalizedException | \Exception $exception) { throw new LocalizedException(__('There are no export file with such name %1', $fileName)); } diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 117783495406a..85d512afaf262 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -495,7 +495,6 @@ public function subscribe($email) $this->sendConfirmationSuccessEmail(); } return $this->getStatus(); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { // phpcs:ignore Magento2.Exceptions.DirectThrow throw new \Exception($e->getMessage()); diff --git a/composer.json b/composer.json index 293cb06ef403c..bc73892dfd2bc 100644 --- a/composer.json +++ b/composer.json @@ -87,7 +87,7 @@ "allure-framework/allure-phpunit": "~1.2.0", "friendsofphp/php-cs-fixer": "~2.14.0", "lusitanian/oauth": "~0.8.10", - "magento/magento-coding-standard": "~3.0.0", + "magento/magento-coding-standard": "~4.0.0", "magento/magento2-functional-testing-framework": "2.4.3", "pdepend/pdepend": "2.5.2", "phpmd/phpmd": "@stable", diff --git a/composer.lock b/composer.lock index f67eb50675314..3a4b14b2dd2d4 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a4299e3f4f0d4dd4915f37a5dde8e2ed", + "content-hash": "390ca0a394a73d3a70dd5a08f7a18674", "packages": [ { "name": "braintree/braintree_php", @@ -201,16 +201,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "33810d865dd06a674130fceb729b2f279dc79e8c" + "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/33810d865dd06a674130fceb729b2f279dc79e8c", - "reference": "33810d865dd06a674130fceb729b2f279dc79e8c", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f26a67e397be0e5c00d7c52ec7b5010098e15ce5", + "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5", "shasum": "" }, "require": { @@ -253,20 +253,20 @@ "ssl", "tls" ], - "time": "2019-07-31T08:13:16+00:00" + "time": "2019-08-02T09:05:43+00:00" }, { "name": "composer/composer", - "version": "1.8.6", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "19b5f66a0e233eb944f134df34091fe1c5dfcc11" + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/19b5f66a0e233eb944f134df34091fe1c5dfcc11", - "reference": "19b5f66a0e233eb944f134df34091fe1c5dfcc11", + "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", "shasum": "" }, "require": { @@ -302,7 +302,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -326,14 +326,14 @@ "homepage": "http://seld.be" } ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", "homepage": "https://getcomposer.org/", "keywords": [ "autoload", "dependency", "package" ], - "time": "2019-06-11T13:03:06+00:00" + "time": "2019-08-02T18:55:33+00:00" }, { "name": "composer/semver", @@ -1552,28 +1552,28 @@ "authors": [ { "name": "Jim Wigginton", - "role": "Lead Developer", - "email": "terrafrost@php.net" + "email": "terrafrost@php.net", + "role": "Lead Developer" }, { "name": "Patrick Monnerat", - "role": "Developer", - "email": "pm@datasphere.ch" + "email": "pm@datasphere.ch", + "role": "Developer" }, { "name": "Andreas Fischer", - "role": "Developer", - "email": "bantu@phpbb.com" + "email": "bantu@phpbb.com", + "role": "Developer" }, { "name": "Hans-Jürgen Petrich", - "role": "Developer", - "email": "petrich@tronic-media.com" + "email": "petrich@tronic-media.com", + "role": "Developer" }, { "name": "Graham Campbell", - "role": "Developer", - "email": "graham@alt-three.com" + "email": "graham@alt-three.com", + "role": "Developer" } ], "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", @@ -2359,16 +2359,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -2380,7 +2380,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2396,13 +2396,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -2413,20 +2413,20 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { @@ -2438,7 +2438,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2472,7 +2472,7 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/process", @@ -3151,16 +3151,16 @@ }, { "name": "zendframework/zend-diactoros", - "version": "1.8.6", + "version": "1.8.7", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "20da13beba0dde8fb648be3cc19765732790f46e" + "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", - "reference": "20da13beba0dde8fb648be3cc19765732790f46e", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/a85e67b86e9b8520d07e6415fcbcb8391b44a75b", + "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b", "shasum": "" }, "require": { @@ -3180,9 +3180,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev", - "dev-develop": "1.9.x-dev", - "dev-release-2.0": "2.0.x-dev" + "dev-release-1.8": "1.8.x-dev" } }, "autoload": { @@ -3211,7 +3209,7 @@ "psr", "psr-7" ], - "time": "2018-09-05T19:29:37+00:00" + "time": "2019-08-06T17:53:53+00:00" }, { "name": "zendframework/zend-escaper", @@ -4931,25 +4929,26 @@ }, { "name": "allure-framework/allure-php-api", - "version": "1.1.4", + "version": "1.1.5", "source": { "type": "git", - "url": "https://github.com/allure-framework/allure-php-adapter-api.git", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" + "url": "https://github.com/allure-framework/allure-php-commons.git", + "reference": "c7a675823ad75b8e02ddc364baae21668e7c4e88" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", + "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/c7a675823ad75b8e02ddc364baae21668e7c4e88", + "reference": "c7a675823ad75b8e02ddc364baae21668e7c4e88", "shasum": "" }, "require": { - "jms/serializer": ">=0.16.0", - "moontoast/math": ">=1.1.0", + "jms/serializer": "^0.16.0", "php": ">=5.4.0", - "phpunit/phpunit": ">=4.0.0", - "ramsey/uuid": ">=3.0.0", - "symfony/http-foundation": ">=2.0" + "ramsey/uuid": "^3.0.0", + "symfony/http-foundation": "^2.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0.0" }, "type": "library", "autoload": { @@ -4967,8 +4966,8 @@ "authors": [ { "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", - "role": "Developer" + "role": "Developer", + "email": "vania-pooh@yandex-team.ru" } ], "description": "PHP API for Allure adapter", @@ -4979,7 +4978,7 @@ "php", "report" ], - "time": "2016-12-07T12:15:46+00:00" + "time": "2018-05-25T14:02:11+00:00" }, { "name": "allure-framework/allure-phpunit", @@ -5910,76 +5909,6 @@ ], "time": "2019-03-25T19:12:02+00:00" }, - { - "name": "doctrine/collections", - "version": "v1.6.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/c5e0bc17b1620e97c968ac409acbff28b8b850be", - "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "require-dev": { - "doctrine/coding-standard": "^6.0", - "phpstan/phpstan-shim": "^0.9.2", - "phpunit/phpunit": "^7.0", - "vimeo/psalm": "^3.2.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", - "homepage": "https://www.doctrine-project.org/projects/collections.html", - "keywords": [ - "array", - "collections", - "iterators", - "php" - ], - "time": "2019-06-09T13:48:14+00:00" - }, { "name": "doctrine/instantiator", "version": "1.2.0", @@ -6096,52 +6025,6 @@ ], "time": "2019-06-08T11:03:04+00:00" }, - { - "name": "epfremme/swagger-php", - "version": "v2.0.0", - "source": { - "type": "git", - "url": "https://github.com/epfremmer/swagger-php.git", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.2", - "doctrine/collections": "^1.3", - "jms/serializer": "^1.1", - "php": ">=5.5", - "phpoption/phpoption": "^1.1", - "symfony/yaml": "^2.7|^3.1" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "~4.8|~5.0", - "satooshi/php-coveralls": "^1.0" - }, - "type": "package", - "autoload": { - "psr-4": { - "Epfremme\\Swagger\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Edward Pfremmer", - "email": "epfremme@nerdery.com" - } - ], - "description": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", - "time": "2016-09-26T17:24:17+00:00" - }, { "name": "facebook/webdriver", "version": "1.7.1", @@ -6476,6 +6359,48 @@ "description": "Expands internal property references in a yaml file.", "time": "2017-12-16T16:06:03+00:00" }, + { + "name": "ircmaxell/password-compat", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ], + "time": "2014-11-20T16:49:30+00:00" + }, { "name": "jms/metadata", "version": "1.7.0", @@ -6568,56 +6493,44 @@ }, { "name": "jms/serializer", - "version": "1.14.0", + "version": "0.16.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca" + "reference": "c8a171357ca92b6706e395c757f334902d430ea9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", - "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9", + "reference": "c8a171357ca92b6706e395c757f334902d430ea9", "shasum": "" }, "require": { - "doctrine/annotations": "^1.0", - "doctrine/instantiator": "^1.0.3", - "jms/metadata": "^1.3", + "doctrine/annotations": "1.*", + "jms/metadata": "~1.1", "jms/parser-lib": "1.*", - "php": "^5.5|^7.0", - "phpcollection/phpcollection": "~0.1", - "phpoption/phpoption": "^1.1" - }, - "conflict": { - "twig/twig": "<1.12" + "php": ">=5.3.2", + "phpcollection/phpcollection": "~0.1" }, "require-dev": { "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "^1.3|^2.0", - "ext-pdo_sqlite": "*", - "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "phpunit/phpunit": "^4.8|^5.0", + "doctrine/phpcr-odm": "~1.0.1", + "jackalope/jackalope-doctrine-dbal": "1.0.*", "propel/propel1": "~1.7", - "psr/container": "^1.0", - "symfony/dependency-injection": "^2.7|^3.3|^4.0", - "symfony/expression-language": "^2.6|^3.0", - "symfony/filesystem": "^2.1", - "symfony/form": "~2.1|^3.0", - "symfony/translation": "^2.1|^3.0", - "symfony/validator": "^2.2|^3.0", - "symfony/yaml": "^2.1|^3.0", - "twig/twig": "~1.12|~2.0" + "symfony/filesystem": "2.*", + "symfony/form": "~2.1", + "symfony/translation": "~2.0", + "symfony/validator": "~2.0", + "symfony/yaml": "2.*", + "twig/twig": ">=1.8,<2.0-dev" }, "suggest": { - "doctrine/cache": "Required if you like to use cache functionality.", - "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", "symfony/yaml": "Required if you'd like to serialize data to YAML format." }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.14-dev" + "dev-master": "0.15-dev" } }, "autoload": { @@ -6627,16 +6540,14 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache2" ], "authors": [ { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" } ], "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", @@ -6648,7 +6559,7 @@ "serialization", "xml" ], - "time": "2019-04-17T08:12:16+00:00" + "time": "2014-03-18T08:39:00+00:00" }, { "name": "league/container", @@ -6784,16 +6695,16 @@ }, { "name": "magento/magento-coding-standard", - "version": "3", + "version": "4", "source": { "type": "git", "url": "https://github.com/magento/magento-coding-standard.git", - "reference": "73a7b7f3c00b02242f45f706571430735586f608" + "reference": "d24e0230a532e19941ed264f57db38fad5b1008a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/73a7b7f3c00b02242f45f706571430735586f608", - "reference": "73a7b7f3c00b02242f45f706571430735586f608", + "url": "https://api.github.com/repos/magento/magento-coding-standard/zipball/d24e0230a532e19941ed264f57db38fad5b1008a", + "reference": "d24e0230a532e19941ed264f57db38fad5b1008a", "shasum": "" }, "require": { @@ -6810,7 +6721,7 @@ "AFL-3.0" ], "description": "A set of Magento specific PHP CodeSniffer rules.", - "time": "2019-06-18T21:01:42+00:00" + "time": "2019-08-06T15:58:37+00:00" }, { "name": "magento/magento2-functional-testing-framework", @@ -6889,23 +6800,23 @@ }, { "name": "mikey179/vfsstream", - "version": "v1.6.6", + "version": "v1.6.7", "source": { "type": "git", "url": "https://github.com/bovigo/vfsStream.git", - "reference": "095238a0711c974ae5b4ebf4c4534a23f3f6c99d" + "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/095238a0711c974ae5b4ebf4c4534a23f3f6c99d", - "reference": "095238a0711c974ae5b4ebf4c4534a23f3f6c99d", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb", + "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "~4.5" + "phpunit/phpunit": "^4.5|^5.0" }, "type": "library", "extra": { @@ -6925,62 +6836,13 @@ "authors": [ { "name": "Frank Kleine", - "homepage": "http://frankkleine.de/", - "role": "Developer" + "role": "Developer", + "homepage": "http://frankkleine.de/" } ], "description": "Virtual file system to mock the real file system in unit tests.", "homepage": "http://vfs.bovigo.org/", - "time": "2019-04-08T13:54:32+00:00" - }, - { - "name": "moontoast/math", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/ramsey/moontoast-math.git", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "shasum": "" - }, - "require": { - "ext-bcmath": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "jakub-onderka/php-parallel-lint": "^0.9.0", - "phpunit/phpunit": "^4.7|>=5.0 <5.4", - "satooshi/php-coveralls": "^0.6.1", - "squizlabs/php_codesniffer": "^2.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Moontoast\\Math\\": "src/Moontoast/Math/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A mathematics library, providing functionality for large numbers", - "homepage": "https://github.com/ramsey/moontoast-math", - "keywords": [ - "bcmath", - "math" - ], - "time": "2017-02-16T16:54:46+00:00" + "time": "2019-08-01T01:38:37+00:00" }, { "name": "mustache/mustache", @@ -9001,31 +8863,31 @@ }, { "name": "symfony/http-foundation", - "version": "v4.3.3", + "version": "v2.8.50", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "8b778ee0c27731105fbf1535f51793ad1ae0ba2b" + "reference": "746f8d3638bf46ee8b202e62f2b214c3d61fb06a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8b778ee0c27731105fbf1535f51793ad1ae0ba2b", - "reference": "8b778ee0c27731105fbf1535f51793ad1ae0ba2b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/746f8d3638bf46ee8b202e62f2b214c3d61fb06a", + "reference": "746f8d3638bf46ee8b202e62f2b214c3d61fb06a", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/mime": "^4.3", - "symfony/polyfill-mbstring": "~1.1" + "php": ">=5.3.9", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php54": "~1.0", + "symfony/polyfill-php55": "~1.0" }, "require-dev": { - "predis/predis": "~1.0", - "symfony/expression-language": "~3.4|~4.0" + "symfony/expression-language": "~2.4|~3.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "2.8-dev" } }, "autoload": { @@ -9052,30 +8914,24 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-07-23T11:21:36+00:00" + "time": "2019-04-16T10:00:53+00:00" }, { - "name": "symfony/mime", + "name": "symfony/options-resolver", "version": "v4.3.3", "source": { "type": "git", - "url": "https://github.com/symfony/mime.git", - "reference": "6b7148029b1dd5eda1502064f06d01357b7b2d8b" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "40762ead607c8f792ee4516881369ffa553fee6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/6b7148029b1dd5eda1502064f06d01357b7b2d8b", - "reference": "6b7148029b1dd5eda1502064f06d01357b7b2d8b", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40762ead607c8f792ee4516881369ffa553fee6f", + "reference": "40762ead607c8f792ee4516881369ffa553fee6f", "shasum": "" }, "require": { - "php": "^7.1.3", - "symfony/polyfill-intl-idn": "^1.10", - "symfony/polyfill-mbstring": "^1.0" - }, - "require-dev": { - "egulias/email-validator": "^2.0", - "symfony/dependency-injection": "~3.4|^4.1" + "php": "^7.1.3" }, "type": "library", "extra": { @@ -9085,7 +8941,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Mime\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -9105,43 +8961,47 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "A library to manipulate MIME messages", + "description": "Symfony OptionsResolver Component", "homepage": "https://symfony.com", "keywords": [ - "mime", - "mime-type" + "config", + "configuration", + "options" ], - "time": "2019-07-19T16:21:19+00:00" + "time": "2019-06-13T11:01:17+00:00" }, { - "name": "symfony/options-resolver", - "version": "v4.3.3", + "name": "symfony/polyfill-php54", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "40762ead607c8f792ee4516881369ffa553fee6f" + "url": "https://github.com/symfony/polyfill-php54.git", + "reference": "a043bcced870214922fbb4bf22679d431ec0296a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40762ead607c8f792ee4516881369ffa553fee6f", - "reference": "40762ead607c8f792ee4516881369ffa553fee6f", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/a043bcced870214922fbb4bf22679d431ec0296a", + "reference": "a043bcced870214922fbb4bf22679d431ec0296a", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" + "Symfony\\Polyfill\\Php54\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -9150,54 +9010,51 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "Symfony polyfill backporting some PHP 5.4+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "config", - "configuration", - "options" + "compatibility", + "polyfill", + "portable", + "shim" ], - "time": "2019-06-13T11:01:17+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/polyfill-intl-idn", - "version": "v1.11.0", + "name": "symfony/polyfill-php55", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af" + "url": "https://github.com/symfony/polyfill-php55.git", + "reference": "548bb39407e78e54f785b4e18c7e0d5d9e493265" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c766e95bec706cdd89903b1eda8afab7d7a6b7af", - "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/548bb39407e78e54f785b4e18c7e0d5d9e493265", + "reference": "548bb39407e78e54f785b4e18c7e0d5d9e493265", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-mbstring": "^1.3", - "symfony/polyfill-php72": "^1.9" - }, - "suggest": { - "ext-intl": "For best performance" + "ircmaxell/password-compat": "~1.0", + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" + "Symfony\\Polyfill\\Php55\\": "" }, "files": [ "bootstrap.php" @@ -9209,38 +9066,36 @@ ], "authors": [ { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Laurent Bassin", - "email": "laurent@bassin.info" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "idn", - "intl", "polyfill", "portable", "shim" ], - "time": "2019-03-04T13:44:35+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "bc4858fb611bda58719124ca079baff854149c89" + "reference": "54b4c428a0054e254223797d2713c31e08610831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", - "reference": "bc4858fb611bda58719124ca079baff854149c89", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", + "reference": "54b4c428a0054e254223797d2713c31e08610831", "shasum": "" }, "require": { @@ -9250,7 +9105,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -9286,20 +9141,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" + "reference": "04ce3335667451138df4307d6a9b61565560199e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", - "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", + "reference": "04ce3335667451138df4307d6a9b61565560199e", "shasum": "" }, "require": { @@ -9308,7 +9163,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -9341,7 +9196,7 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/service-contracts", @@ -9453,20 +9308,20 @@ }, { "name": "symfony/yaml", - "version": "v3.4.30", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b" + "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/051d045c684148060ebfc9affb7e3f5e0899d40b", - "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b", + "url": "https://api.github.com/repos/symfony/yaml/zipball/34d29c2acd1ad65688f58452fd48a46bd996d5a6", + "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -9481,7 +9336,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -9508,7 +9363,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-07-24T13:01:31+00:00" + "time": "2019-07-24T14:47:54+00:00" }, { "name": "theseer/fdomdocument", diff --git a/lib/internal/Magento/Framework/App/Config/Initial/Reader.php b/lib/internal/Magento/Framework/App/Config/Initial/Reader.php index 076d6f1194351..99ec3d3f0280f 100644 --- a/lib/internal/Magento/Framework/App/Config/Initial/Reader.php +++ b/lib/internal/Magento/Framework/App/Config/Initial/Reader.php @@ -108,7 +108,6 @@ public function read() } else { $domDocument->merge($file); } - // phpcs:ignore Magento2.Exceptions.ThrowCatch.ThrowCatch } catch (\Magento\Framework\Config\Dom\ValidationException $e) { throw new \Magento\Framework\Exception\LocalizedException( new \Magento\Framework\Phrase( diff --git a/lib/internal/Magento/Framework/Session/SessionManager.php b/lib/internal/Magento/Framework/Session/SessionManager.php index a6f8d7b86bf90..b96925facf528 100644 --- a/lib/internal/Magento/Framework/Session/SessionManager.php +++ b/lib/internal/Magento/Framework/Session/SessionManager.php @@ -183,8 +183,6 @@ public function start() try { $this->appState->getAreaCode(); - // @todo MC-18221 need to fix check false positive - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { throw new \Magento\Framework\Exception\SessionException( new \Magento\Framework\Phrase( From d8add8a90b22ce58f6688e3d6677c7623080fbe7 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p.bystritsky@yandex.ru> Date: Mon, 22 Jul 2019 15:01:03 +0300 Subject: [PATCH 160/841] magento/magento2#19230: Refactoring and unit test update. --- .../Sales/Model/Order/CustomerAssignment.php | 18 +- .../Model/Order/CustomerAssigmentTest.php | 155 ++++++++++++++++++ .../AssignOrderToCustomerObserverTest.php | 23 ++- ...CouponDataAfterOrderCustomerAssignTest.php | 7 +- 4 files changed, 186 insertions(+), 17 deletions(-) create mode 100644 app/code/Magento/Sales/Test/Unit/Model/Order/CustomerAssigmentTest.php diff --git a/app/code/Magento/Sales/Model/Order/CustomerAssignment.php b/app/code/Magento/Sales/Model/Order/CustomerAssignment.php index eb4e790c311ca..194b03c2bc9bd 100644 --- a/app/code/Magento/Sales/Model/Order/CustomerAssignment.php +++ b/app/code/Magento/Sales/Model/Order/CustomerAssignment.php @@ -7,10 +7,10 @@ namespace Magento\Sales\Model\Order; -use Magento\Sales\Api\Data\OrderInterface; -use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Customer\Api\Data\CustomerInterface; use Magento\Framework\Event\ManagerInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; /** * Assign customer to order. @@ -47,10 +47,18 @@ public function __construct( * @param OrderInterface $order * @param CustomerInterface $customer */ - public function execute(OrderInterface $order, CustomerInterface $customer)/*: void*/ + public function execute(OrderInterface $order, CustomerInterface $customer): void { - $order->setCustomerId($customer->getId()); - $order->setCustomerIsGuest(false); + $order->setCustomerId($customer->getId()) + ->setCustomerIsGuest(false) + ->setCustomerEmail($customer->getEmail()) + ->setCustomerFirstname($customer->getFirstname()) + ->setCustomerLastname($customer->getLastname()) + ->setCustomerMiddlename($customer->getMiddlename()) + ->setCustomerPrefix($customer->getPrefix()) + ->setCustomerSuffix($customer->getSuffix()) + ->setCustomerGroupId($customer->getGroupId()); + $this->orderRepository->save($order); $this->eventManager->dispatch( diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/CustomerAssigmentTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/CustomerAssigmentTest.php new file mode 100644 index 0000000000000..2ceda3018befd --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/CustomerAssigmentTest.php @@ -0,0 +1,155 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Model\Order; + +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order\CustomerAssignment; + +/** + * Test for Magento\Sales\Model\Order\CustomerAssignment class. + */ +class CustomerAssigmentTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var CustomerAssignment + */ + private $customerAssignment; + + /** + * @var OrderInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $orderMock; + + /** + * @var CustomerInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $customerMock; + + /** + * @var OrderRepositoryInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $orderRepositoryMock; + + /** + * @var ManagerInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $eventManagerMock; + + /** + * Tests 'execute' method. + * + * @dataProvider executeDataProvider + * @param array $data + */ + public function testExecute(array $data): void + { + $this->configureOrderMock($data); + $this->configureCustomerMock($data); + $this->orderRepositoryMock->expects($this->once())->method('save')->with($this->orderMock); + $this->eventManagerMock->expects($this->once())->method('dispatch')->with( + 'sales_order_customer_assign_after', + [ + 'order' => $this->orderMock, + 'customer' => $this->customerMock + ] + ); + + $this->customerAssignment->execute($this->orderMock, $this->customerMock); + } + + /** + * + * Data provider for testExecute. + * @return array + */ + public function executeDataProvider(): array + { + return [ + [ + [ + 'customerId' => 1, + 'customerIsGuest' => false, + 'customerEmail' => 'customerEmail', + 'customerFirstname' => 'customerFirstname', + 'customerLastname' => 'customerLastname', + 'customerMiddlename' => 'customerMiddlename', + 'customerPrefix' => 'customerPrefix', + 'customerSuffix' => 'customerSuffix', + 'customerGroupId' => 'customerGroupId', + ], + ], + ]; + } + + /** + * @return void + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + $this->orderMock = $this->createMock(OrderInterface::class); + $this->customerMock = $this->createMock(CustomerInterface::class); + $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class); + $this->eventManagerMock = $this->createMock(ManagerInterface::class); + $this->customerAssignment = $objectManager->getObject( + CustomerAssignment::class, + [ + 'eventManager' => $this->eventManagerMock, + 'orderRepository' => $this->orderRepositoryMock + ] + ); + } + + /** + * Set up order mock. + * + * @param array $data + */ + private function configureOrderMock(array $data): void + { + $this->orderMock->expects($this->once())->method('setCustomerId')->with($data['customerId']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerIsGuest')->with($data['customerIsGuest']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerEmail')->with($data['customerEmail']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerFirstname')->with($data['customerFirstname']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerLastname')->with($data['customerLastname']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerMiddlename')->with($data['customerMiddlename']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerPrefix')->with($data['customerPrefix']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerSuffix')->with($data['customerSuffix']) + ->willReturn($this->orderMock); + $this->orderMock->expects($this->once())->method('setCustomerGroupId')->with($data['customerGroupId']) + ->willReturn($this->orderMock); + } + + /** + * Set up customer mock. + * + * @param array $data + */ + private function configureCustomerMock(array $data): void + { + $this->customerMock->expects($this->once())->method('getId')->willReturn($data['customerId']); + $this->customerMock->expects($this->once())->method('getEmail')->willReturn($data['customerEmail']); + $this->customerMock->expects($this->once())->method('getFirstname')->willReturn($data['customerFirstname']); + $this->customerMock->expects($this->once())->method('getLastname')->willReturn($data['customerLastname']); + $this->customerMock->expects($this->once())->method('getMiddlename')->willReturn($data['customerMiddlename']); + $this->customerMock->expects($this->once())->method('getPrefix')->willReturn($data['customerPrefix']); + $this->customerMock->expects($this->once())->method('getSuffix')->willReturn($data['customerSuffix']); + $this->customerMock->expects($this->once())->method('getGroupId')->willReturn($data['customerGroupId']); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php index 18371274049e3..80ab948a9ad71 100644 --- a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php +++ b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php @@ -51,10 +51,11 @@ protected function setUp() * Test assigning order to customer after issuing guest order * * @dataProvider getCustomerIds + * @param null|int $orderCustomerId * @param null|int $customerId * @return void */ - public function testAssignOrderToCustomerAfterGuestOrder($customerId) + public function testAssignOrderToCustomerAfterGuestOrder($orderCustomerId, $customerId) { $orderId = 1; /** @var Observer|PHPUnit_Framework_MockObject_MockObject $observerMock */ @@ -71,15 +72,18 @@ public function testAssignOrderToCustomerAfterGuestOrder($customerId) ->getMockForAbstractClass(); $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); $eventMock->expects($this->any())->method('getData') - ->willReturnMap([ - ['delegate_data', null, ['__sales_assign_order_id' => $orderId]], - ['customer_data_object', null, $customerMock] - ]); - $orderMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + ->willReturnMap( + [ + ['delegate_data', null, ['__sales_assign_order_id' => $orderId]], + ['customer_data_object', null, $customerMock] + ] + ); + $orderMock->expects($this->once())->method('getCustomerId')->willReturn($orderCustomerId); $this->orderRepositoryMock->expects($this->once())->method('get')->with($orderId) ->willReturn($orderMock); - if ($customerId) { + if (!$orderCustomerId) { + $customerMock->expects($this->once())->method('getId')->willReturn($customerId); $this->assignmentMock->expects($this->once())->method('execute')->with($orderMock, $customerMock); $this->sut->execute($observerMock); return; @@ -96,6 +100,9 @@ public function testAssignOrderToCustomerAfterGuestOrder($customerId) */ public function getCustomerIds() { - return [[null, 1]]; + return [ + [null, 1], + [1, 1], + ]; } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php index 0452666bdff44..9eaca30e4bd5d 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Observer/AssignCouponDataAfterOrderCustomerAssignTest.php @@ -6,14 +6,14 @@ namespace Magento\SalesRule\Model\Observer; +use Magento\Customer\Model\Data\Customer; +use Magento\Customer\Model\GroupManagement; use Magento\Framework\Controller\Result\Redirect; use Magento\Sales\Model\Order; -use Magento\Customer\Model\GroupManagement; use Magento\SalesRule\Api\CouponRepositoryInterface; use Magento\SalesRule\Model\Coupon; use Magento\SalesRule\Model\Rule; use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Model\Data\Customer; use Magento\TestFramework\Helper\Bootstrap; /** @@ -252,9 +252,8 @@ private function makeOrderWithCouponAsGuest(Coupon $coupon) : Order /** * @param Order $order - * @return Redirect */ - private function delegateOrderToBeAssigned(Order $order): Redirect + private function delegateOrderToBeAssigned(Order $order) { $this->delegateCustomerService->delegateNew($order->getId()); } From bd5b7c584de95da97200be47d6f63fd9eda372f7 Mon Sep 17 00:00:00 2001 From: Stsiapan Korf <Stsiapan_Korf@epam.com> Date: Wed, 7 Aug 2019 12:58:54 +0000 Subject: [PATCH 161/841] MAGETWO-98748: Incorrect behavior in the category menu on the Storefront - Add cache key info about active category --- .../Magento/Catalog/Plugin/Block/Topmenu.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php index 44f9193ab4012..d788525356fab 100644 --- a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php +++ b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php @@ -193,4 +193,21 @@ protected function getCategoryTree($storeId, $rootId) return $collection; } + + /** + * Add active + * + * @param \Magento\Theme\Block\Html\Topmenu $subject + * @param string[] $result + * @return string[] + */ + public function afterGetCacheKeyInfo(\Magento\Theme\Block\Html\Topmenu $subject, array $result) + { + $activeCategory = $this->getCurrentCategory(); + if ($activeCategory) { + $result[] = Category::CACHE_TAG . '_' . $activeCategory->getId(); + } + + return $result; + } } From 7c4032176af6f943f369f309c1a8e494f7ceaf48 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Wed, 7 Aug 2019 21:18:12 +0700 Subject: [PATCH 162/841] Resolve No alert when user "Apply empty Coupon Code" in create new order form (issue 24015) --- .../templates/order/create/coupons/form.phtml | 18 +++++++++++++++-- .../adminhtml/web/order/create/scripts.js | 20 +++++++------------ 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/coupons/form.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/coupons/form.phtml index 6612b60e998b0..87ef29c7d42ed 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/coupons/form.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/coupons/form.phtml @@ -12,7 +12,7 @@ <div class="admin__field-control"> <?php if (!$block->getCouponCode()) : ?> <input type="text" class="admin__control-text" id="coupons:code" value="" name="coupon_code" /> - <?= $block->getButtonHtml(__('Apply'), 'order.applyCoupon($F(\'coupons:code\'))') ?> + <?= $block->getButtonHtml(__('Apply'), 'order.handleOnclickCoupon($F(\'coupons:code\'))') ?> <?php endif; ?> <?php if ($block->getCouponCode()) : ?> <p class="added-coupon-code"> @@ -22,9 +22,23 @@ </p> <?php endif; ?> <script> - require(["Magento_Sales/order/create/form"], function() { + require([ + "jquery", + 'Magento_Ui/js/modal/alert', + 'mage/translate', + "Magento_Sales/order/create/form" + ], function($, alert) { order.overlay('shipping-method-overlay', <?php if ($block->getQuote()->isVirtual()) : ?>false<?php else : ?>true<?php endif; ?>); order.overlay('address-shipping-overlay', <?php if ($block->getQuote()->isVirtual()) : ?>false<?php else : ?>true<?php endif; ?>); + order.handleOnclickCoupon = function (code) { + if (!code) { + alert({ + content: $.mage.__('Please enter a coupon code!') + }); + } else { + order.applyCoupon(code); + } + }; }); </script> </div> diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js index afc5b3e5243c7..3fe9d08782880 100644 --- a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js +++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js @@ -445,8 +445,8 @@ define([ */ loadShippingRates: function () { var addressContainer = this.shippingAsBilling ? - 'billingAddressContainer' : - 'shippingAddressContainer', + 'billingAddressContainer' : + 'shippingAddressContainer', data = this.serializeData(this[addressContainer]).toObject(); data['collect_shipping_rates'] = 1; @@ -571,17 +571,11 @@ define([ }, applyCoupon : function(code){ - if (!code) { - alert({ - content: jQuery.mage.__('Please enter a coupon code!') - }); - } else { - this.loadArea(['items', 'shipping_method', 'totals', 'billing_method'], true, {'order[coupon][code]':code, reset_shipping: 0}); - this.orderItemChanged = false; - jQuery('html, body').animate({ - scrollTop: 0 - }); - } + this.loadArea(['items', 'shipping_method', 'totals', 'billing_method'], true, {'order[coupon][code]':code, reset_shipping: 0}); + this.orderItemChanged = false; + jQuery('html, body').animate({ + scrollTop: 0 + }); }, addProduct : function(id){ From 8dd0ed628fabc94d3a0c131042c0b2d8412bba92 Mon Sep 17 00:00:00 2001 From: Kristian Charbonneau <kcharbonneau@somethingdigital.com> Date: Wed, 7 Aug 2019 11:55:31 -0400 Subject: [PATCH 163/841] Update stock status on CSV Product import according to quantity If the is_in_stock field is not present in a particular row in the CSV file and the qty field is set to 0, the default is_in_stock value is overriden from 1 to 0. --- app/code/Magento/CatalogImportExport/Model/Import/Product.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 5c083d421f0e1..8ad8458c1be27 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -2972,6 +2972,10 @@ private function formatStockDataForRow(array $rowData): array $stockItemDo = $this->stockRegistry->getStockItem($row['product_id'], $row['website_id']); $existStockData = $stockItemDo->getData(); + if (isset($rowData['qty']) && $rowData['qty'] == 0 && !isset($rowData['is_in_stock'])) { + $rowData['is_in_stock'] = 0; + } + $row = array_merge( $this->defaultStockData, array_intersect_key($existStockData, $this->defaultStockData), From 50d68d43dbd88bc6446bb505f754d4446b9f1e0f Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Wed, 7 Aug 2019 19:58:13 +0200 Subject: [PATCH 164/841] Some CS fixes --- app/code/Magento/CustomerDownloadableGraphQl/composer.json | 2 +- .../Magento/CustomerDownloadableGraphQl/etc/schema.graphqls | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/CustomerDownloadableGraphQl/composer.json b/app/code/Magento/CustomerDownloadableGraphQl/composer.json index de2ef4d133242..84038c6b7a883 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/composer.json +++ b/app/code/Magento/CustomerDownloadableGraphQl/composer.json @@ -3,7 +3,7 @@ "description": "N/A", "type": "magento2-module", "require": { - "php": "~7.1.3||~7.2.0", + "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-catalog": "*", "magento/module-downloadable": "*", "magento/module-downloadable-graph-ql": "*", diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls index eafd762329e81..5ff0dc664ff74 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls @@ -15,4 +15,4 @@ type CustomerDownloadableProduct { status: String download_url: String remaining_downloads: String -} \ No newline at end of file +} From 2fbd1edbca371953eab2dd4c4cdb8a98b5a7f77c Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Thu, 8 Aug 2019 14:25:49 +0700 Subject: [PATCH 165/841] Resolve ImportExport module missing some translation --- app/code/Magento/ImportExport/i18n/en_US.csv | 2 ++ .../view/adminhtml/templates/export/form/before.phtml | 2 +- .../view/adminhtml/templates/import/form/before.phtml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/ImportExport/i18n/en_US.csv b/app/code/Magento/ImportExport/i18n/en_US.csv index d7680a71ac5f7..5787d6f7d02b6 100644 --- a/app/code/Magento/ImportExport/i18n/en_US.csv +++ b/app/code/Magento/ImportExport/i18n/en_US.csv @@ -123,3 +123,5 @@ Summary,Summary "New product data is added to existing product data entries in the database. All fields except SKU can be updated.","New product data is added to existing product data entries in the database. All fields except SKU can be updated." "All existing product data is replaced with the imported new data. <b>Exercise caution when replacing data. All existing product data will be completely cleared and all references in the system will be lost.</b>","All existing product data is replaced with the imported new data. <b>Exercise caution when replacing data. All existing product data will be completely cleared and all references in the system will be lost.</b>" "Any entities in the import data that match existing entities in the database are deleted from the database.","Any entities in the import data that match existing entities in the database are deleted from the database." +"Invalid data","Invalid data" +"Invalid response","Invalid response" \ No newline at end of file diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml index 26b241b999493..d097f0977f291 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml +++ b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml @@ -89,7 +89,7 @@ require([ form.action = oldAction; } else { alert({ - content: 'Invalid data' + content: '<?php echo __('Invalid data'); ?>' }); } }; diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml index 628c1088a016c..80ebe76361ece 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml +++ b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml @@ -209,7 +209,7 @@ require([ postToFrameProcessResponse: function(response) { if ('object' != typeof(response)) { alert({ - content: 'Invalid response' + content: '<?php echo __('Invalid response'); ?>' }); return false; From 87c8e2ea8e400cf544756faf76316ae0d10c6d85 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Thu, 8 Aug 2019 16:28:44 +0700 Subject: [PATCH 166/841] Fix static test --- .../view/adminhtml/templates/export/form/before.phtml | 2 +- .../view/adminhtml/templates/import/form/before.phtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml index d097f0977f291..3e7a19a0c0d82 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml +++ b/app/code/Magento/ImportExport/view/adminhtml/templates/export/form/before.phtml @@ -89,7 +89,7 @@ require([ form.action = oldAction; } else { alert({ - content: '<?php echo __('Invalid data'); ?>' + content: '<?= $block->escapeHtml(__('Invalid data')); ?>' }); } }; diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml index 80ebe76361ece..bd88ec419d848 100644 --- a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml +++ b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/before.phtml @@ -209,7 +209,7 @@ require([ postToFrameProcessResponse: function(response) { if ('object' != typeof(response)) { alert({ - content: '<?php echo __('Invalid response'); ?>' + content: '<?= $block->escapeHtml(__('Invalid response')); ?>' }); return false; From 0c6c410bdd28586e98db416ce422e7264d0ec92f Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 8 Aug 2019 15:24:25 +0300 Subject: [PATCH 167/841] MC-17630: Selector in coupons grid does not respect select all/select visible values --- .../Backend/Block/Widget/Grid/Extended.php | 2 +- .../Block/Widget/Grid/Massaction/Extended.php | 31 +++- .../Magento/Review/Block/Adminhtml/Grid.php | 17 +- .../Promo/Quote/Edit/Tab/Coupons/Grid.php | 27 ++-- .../Promo/Quote/Edit/Tab/Coupons/GridTest.php | 151 ++++++++++++++++++ .../_files/cart_rule_with_coupon_list.php | 45 ++++++ .../cart_rule_with_coupon_list_rollback.php | 17 ++ 7 files changed, 262 insertions(+), 28 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/GridTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Extended.php b/app/code/Magento/Backend/Block/Widget/Grid/Extended.php index 40e87171e82cc..a0307abf5654d 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Extended.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Extended.php @@ -588,7 +588,7 @@ public function setMassactionBlockName($blockName) /** * Retrieve massaction block * - * @return $this + * @return \Magento\Backend\Block\Widget\Grid\Massaction\Extended */ public function getMassactionBlock() { diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php index 8e0fce2b16cc9..28200323a3aec 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/Extended.php @@ -3,9 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Backend\Block\Widget\Grid\Massaction; +use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\DB\Select; + /** * Grid widget massaction block * @@ -13,7 +15,7 @@ * @deprecated 100.2.0 in favour of UI component implementation * @method \Magento\Quote\Model\Quote setHideFormElement(boolean $value) Hide Form element to prevent IE errors * @method boolean getHideFormElement() - * @author Magento Core Team <core@magentocommerce.com> + * @author Magento Core Team <core@magentocommerce.com> * @TODO MAGETWO-31510: Remove deprecated class * @since 100.0.2 */ @@ -156,7 +158,7 @@ public function getItemsJson() */ public function getCount() { - return sizeof($this->_items); + return count($this->_items); } /** @@ -248,6 +250,8 @@ public function getApplyButtonHtml() } /** + * Get mass action javascript code + * * @return string */ public function getJavaScript() @@ -264,6 +268,8 @@ public function getJavaScript() } /** + * Get grid ids in JSON format + * * @return string */ public function getGridIdsJson() @@ -281,15 +287,24 @@ public function getGridIdsJson() $massActionIdField = $this->getParentBlock()->getMassactionIdField(); } - $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); - - if (!empty($gridIds)) { - return join(",", $gridIds); + if ($allIdsCollection instanceof AbstractDb) { + $idsSelect = clone $allIdsCollection->getSelect(); + $idsSelect->reset(Select::ORDER); + $idsSelect->reset(Select::LIMIT_COUNT); + $idsSelect->reset(Select::LIMIT_OFFSET); + $idsSelect->reset(Select::COLUMNS); + $idsSelect->columns($massActionIdField); + $idList = $allIdsCollection->getConnection()->fetchCol($idsSelect); + } else { + $idList = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField); } - return ''; + + return implode(',', $idList); } /** + * Retrieve massaction block js object name + * * @return string */ public function getHtmlId() diff --git a/app/code/Magento/Review/Block/Adminhtml/Grid.php b/app/code/Magento/Review/Block/Adminhtml/Grid.php index ed44194dffdf8..becb3b683b3dc 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Grid.php +++ b/app/code/Magento/Review/Block/Adminhtml/Grid.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +namespace Magento\Review\Block\Adminhtml; /** * Adminhtml reviews grid @@ -13,11 +14,7 @@ * @method \Magento\Review\Block\Adminhtml\Grid setCustomerId() setCustomerId(int $customerId) * @method \Magento\Review\Block\Adminhtml\Grid setMassactionIdFieldOnlyIndexValue() * setMassactionIdFieldOnlyIndexValue(bool $onlyIndex) - * - * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Review\Block\Adminhtml; - class Grid extends \Magento\Backend\Block\Widget\Grid\Extended { /** @@ -349,6 +346,18 @@ protected function _prepareMassaction() ); } + /** + * @inheritdoc + */ + protected function _prepareMassactionColumn() + { + parent::_prepareMassactionColumn(); + /** needs for correct work of mass action select functionality */ + $this->setMassactionIdField('rt.review_id'); + + return $this; + } + /** * Get row url * diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/Grid.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/Grid.php index 20014c3c02705..bf92e21827a01 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/Grid.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/Grid.php @@ -46,9 +46,7 @@ public function __construct( } /** - * Constructor - * - * @return void + * @inheritdoc */ protected function _construct() { @@ -58,9 +56,7 @@ protected function _construct() } /** - * Prepare collection for grid - * - * @return $this + * @inheritdoc */ protected function _prepareCollection() { @@ -71,15 +67,20 @@ protected function _prepareCollection() */ $collection = $this->_salesRuleCoupon->create()->addRuleToFilter($priceRule)->addGeneratedCouponsFilter(); + if ($this->_isExport && $this->getMassactionBlock()->isAvailable()) { + $itemIds = $this->getMassactionBlock()->getSelected(); + if (!empty($itemIds)) { + $collection->addFieldToFilter('coupon_id', ['in' => $itemIds]); + } + } + $this->setCollection($collection); return parent::_prepareCollection(); } /** - * Define grid columns - * - * @return $this + * @inheritdoc */ protected function _prepareColumns() { @@ -121,9 +122,7 @@ protected function _prepareColumns() } /** - * Configure grid mass actions - * - * @return $this + * @inheritdoc */ protected function _prepareMassaction() { @@ -146,9 +145,7 @@ protected function _prepareMassaction() } /** - * Get grid url - * - * @return string + * @inheritdoc */ public function getGridUrl() { diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/GridTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/GridTest.php new file mode 100644 index 0000000000000..c5b45e662db7b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Block/Adminhtml/Promo/Quote/Edit/Tab/Coupons/GridTest.php @@ -0,0 +1,151 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Block\Adminhtml\Promo\Quote\Edit\Tab\Coupons; + +use Magento\Backend\Block\Widget\Grid\Massaction\Extended as MassActionBlock; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Registry; +use Magento\Framework\View\LayoutInterface; +use Magento\SalesRule\Model\RegistryConstants; +use Magento\SalesRule\Model\ResourceModel\Rule\Collection as RuleCollection; +use Magento\SalesRule\Model\Rule; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Class to test Coupon codes grid + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/SalesRule/_files/cart_rule_with_coupon_list.php + * @see \Magento\SalesRule\Block\Adminhtml\Promo\Quote\Edit\Tab\Coupons\Grid + */ +class GridTest extends TestCase +{ + /** + * @var LayoutInterface + */ + private $layout; + + /** + * @var Rule + */ + private $salesRule; + + /** + * @var Registry + */ + private $registry; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $this->resourceConnection = Bootstrap::getObjectManager()->get(ResourceConnection::class); + $this->registry = Bootstrap::getObjectManager()->get(Registry::class); + + $this->initSalesRule(); + $this->prepareLayout(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + + $this->registry->unregister(RegistryConstants::CURRENT_SALES_RULE); + } + + /** + * Check if mass action block exists + */ + public function testMassActionBlockExists() + { + $this->assertNotFalse( + $this->getMassActionBlock(), + 'Mass action block does not exist in the grid, or it name was changed.' + ); + } + + /** + * Check if function returns correct result + */ + public function testMassActionBlockContainsCorrectIdList() + { + $this->assertEquals( + implode(',', $this->getCouponsIdList()), + $this->getMassActionBlock()->getGridIdsJson(), + 'Function returns incorrect result.' + ); + } + + /** + * Retrieve mass action block + * + * @return bool|MassActionBlock + */ + private function getMassActionBlock() + { + /** @var Grid $grid */ + $grid = $this->layout->getBlock('sales_rule_quote_edit_tab_coupons_grid'); + + return $grid->getMassactionBlock(); + } + + /** + * Prepare layout blocks + */ + private function prepareLayout() + { + $this->layout = Bootstrap::getObjectManager()->create(LayoutInterface::class); + $this->layout->getUpdate()->load('sales_rule_promo_quote_couponsgrid'); + $this->layout->generateXml(); + $this->layout->generateElements(); + + $grid = $this->layout->getBlock('sales_rule_quote_edit_tab_coupons_grid'); + $grid->toHtml(); + } + + /** + * Init current sales rule + */ + private function initSalesRule() + { + /** @var RuleCollection $collection */ + $collection = Bootstrap::getObjectManager()->create(RuleCollection::class); + $collection->addFieldToFilter('name', 'Rule with coupon list'); + $this->salesRule = $collection->getFirstItem(); + $this->registry->register(RegistryConstants::CURRENT_SALES_RULE, $this->salesRule); + } + + /** + * Retrieve id list of coupons + * + * @return array + */ + private function getCouponsIdList(): array + { + $select = $this->resourceConnection->getConnection() + ->select() + ->from($this->resourceConnection->getTableName('salesrule_coupon')) + ->columns(['coupon_id']) + ->where('rule_id=?', $this->salesRule->getId()); + + return $this->resourceConnection->getConnection()->fetchCol($select); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php new file mode 100644 index 0000000000000..17bdd4feeb75b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Model\GroupManagement; +use Magento\SalesRule\Api\CouponRepositoryInterface; +use Magento\SalesRule\Model\Coupon; +use Magento\SalesRule\Model\Rule; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Rule $salesRule */ +$salesRule = $objectManager->create(Rule::class); +$salesRule->setData( + [ + 'name' => 'Rule with coupon list', + 'is_active' => 1, + 'customer_group_ids' => [GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => Rule::COUPON_TYPE_SPECIFIC, + 'simple_action' => Rule::CART_FIXED_ACTION, + 'discount_amount' => 10, + 'discount_step' => 0, + 'stop_rules_processing' => 1, + 'use_auto_generation' => 1, + 'website_ids' => [ + $objectManager->get(StoreManagerInterface::class)->getWebsite()->getId(), + ], + ] +); +$objectManager->get(\Magento\SalesRule\Model\ResourceModel\Rule::class)->save($salesRule); + +/* @var CouponRepositoryInterface $couponRepository */ +$couponRepository = $objectManager->get(CouponRepositoryInterface::class); +for ($index = 1; $index <= 100; $index ++) { + $coupon = $objectManager->create(Coupon::class); + $coupon->setRuleId($salesRule->getId()) + ->setCode('coupon_code_' . $index) + ->setType(1); + $couponRepository->save($coupon); +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php new file mode 100644 index 0000000000000..94f36d72f2363 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\SalesRule\Model\ResourceModel\Rule\Collection as RuleCollection; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var RuleCollection $collection */ +$collection = Bootstrap::getObjectManager()->create(RuleCollection::class); +$collection->addFieldToFilter('name', 'Rule with coupon list'); +$rule = $collection->getFirstItem(); +if ($rule->getId()) { + $rule->delete(); +} From d24d65fe993f32f8dd087ee0273f3f3d39313dae Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 8 Aug 2019 15:27:51 +0300 Subject: [PATCH 168/841] MC-17630: Selector in coupons grid does not respect select all/select visible values --- .../SalesRule/_files/cart_rule_with_coupon_list.php | 2 +- .../_files/cart_rule_with_coupon_list_rollback.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php index 17bdd4feeb75b..1fe2bb042de8b 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list.php @@ -36,7 +36,7 @@ /* @var CouponRepositoryInterface $couponRepository */ $couponRepository = $objectManager->get(CouponRepositoryInterface::class); -for ($index = 1; $index <= 100; $index ++) { +for ($index = 1; $index <= 30; $index ++) { $coupon = $objectManager->create(Coupon::class); $coupon->setRuleId($salesRule->getId()) ->setCode('coupon_code_' . $index) diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php index 94f36d72f2363..44a91e5810141 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/cart_rule_with_coupon_list_rollback.php @@ -8,6 +8,11 @@ use Magento\SalesRule\Model\ResourceModel\Rule\Collection as RuleCollection; use Magento\TestFramework\Helper\Bootstrap; +/** @var \Magento\Framework\Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + /** @var RuleCollection $collection */ $collection = Bootstrap::getObjectManager()->create(RuleCollection::class); $collection->addFieldToFilter('name', 'Rule with coupon list'); @@ -15,3 +20,6 @@ if ($rule->getId()) { $rule->delete(); } + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From 38c3fdb01a2be6e24d8e734799bc4c535e7ca6fb Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 8 Aug 2019 17:27:32 +0300 Subject: [PATCH 169/841] MC-17630: Selector in coupons grid does not respect select all/select visible values --- app/code/Magento/Backend/Block/Widget/Grid/Extended.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Extended.php b/app/code/Magento/Backend/Block/Widget/Grid/Extended.php index a0307abf5654d..40e87171e82cc 100644 --- a/app/code/Magento/Backend/Block/Widget/Grid/Extended.php +++ b/app/code/Magento/Backend/Block/Widget/Grid/Extended.php @@ -588,7 +588,7 @@ public function setMassactionBlockName($blockName) /** * Retrieve massaction block * - * @return \Magento\Backend\Block\Widget\Grid\Massaction\Extended + * @return $this */ public function getMassactionBlock() { From 5f27338cf8d588bf29cca7a6bc62ebda2a0069c7 Mon Sep 17 00:00:00 2001 From: Vitalii Tychenok <v.tychenok@ism-ukraine.com> Date: Thu, 8 Aug 2019 17:52:42 +0300 Subject: [PATCH 170/841] Set isMultiShipping to 0 in database so it can be used correctly by rest api requests on checkout page --- app/code/Magento/Multishipping/Controller/Checkout/Plugin.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php index edd313c106ed3..dd47949ea408f 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php @@ -31,5 +31,6 @@ public function __construct(\Magento\Checkout\Model\Cart $cart) public function beforeExecute(\Magento\Framework\App\Action\Action $subject) { $this->cart->getQuote()->setIsMultiShipping(0); + $this->cart->saveQuote(); } } From 5009bb88d3fdedfb5efdb71d0d2b9b9cb8efabb2 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Thu, 8 Aug 2019 18:22:15 +0300 Subject: [PATCH 171/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" - Fix mftf --- .../Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml index eb014ca7f884d..5b6207e135796 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml @@ -40,6 +40,8 @@ <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteTestWebsite"> <argument name="websiteName" value="{{customWebsite.name}}"/> </actionGroup> + <actionGroup ref="GoToProductCatalogPage" stepKey="goToProductCatalogPage"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGridColumnsInitial"/> <actionGroup ref="ResetWebUrlOptions" stepKey="resetUrlOption"/> <magentoCLI command="indexer:reindex" stepKey="reindex"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> From 113bf43cb9821f67ef1b6ed90d5984328a02d7d5 Mon Sep 17 00:00:00 2001 From: Mastiuhin Olexandr <mastiuhin.olexandr@transoftgroup.com> Date: Thu, 8 Aug 2019 20:02:35 +0300 Subject: [PATCH 172/841] MC-18194: Saved Credit Card Feature with Vault is not displaying proper information of card in order information page --- app/code/Magento/Vault/Model/Method/Vault.php | 92 +++++++++++-------- .../Test/Unit/Model/Method/VaultTest.php | 29 +++++- .../Test/TestCase/ReorderUsingVaultTest.xml | 5 + .../Sales/Test/TestStep/SubmitOrderStep.php | 2 +- .../_files/fake_payment_token.php | 2 + 5 files changed, 90 insertions(+), 40 deletions(-) diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index 2de934072daa3..5b90ecc86750d 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -21,6 +21,7 @@ use Magento\Vault\Api\PaymentTokenManagementInterface; use Magento\Vault\Block\Form; use Magento\Vault\Model\VaultPaymentInterface; +use Magento\Framework\Serialize\Serializer\Json; /** * Class Vault @@ -103,6 +104,11 @@ class Vault implements VaultPaymentInterface */ private $code; + /** + * @var Json + */ + private $jsonSerializer; + /** * Constructor * @@ -116,6 +122,7 @@ class Vault implements VaultPaymentInterface * @param PaymentTokenManagementInterface $tokenManagement * @param OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory * @param string $code + * @param Json $jsonSerializer * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -129,7 +136,8 @@ public function __construct( Command\CommandManagerPoolInterface $commandManagerPool, PaymentTokenManagementInterface $tokenManagement, OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory, - $code + $code, + Json $jsonSerializer ) { $this->config = $config; $this->configFactory = $configFactory; @@ -141,21 +149,14 @@ public function __construct( $this->tokenManagement = $tokenManagement; $this->paymentExtensionFactory = $paymentExtensionFactory; $this->code = $code; - } - - /** - * @return MethodInterface - */ - private function getVaultProvider() - { - return $this->vaultProvider; + $this->jsonSerializer = $jsonSerializer; } /** * Unifies configured value handling logic * * @param string $field - * @param null $storeId + * @param int|null $storeId * @return mixed */ private function getConfiguredValue($field, $storeId = null) @@ -226,8 +227,8 @@ public function canOrder() */ public function canAuthorize() { - return $this->getVaultProvider()->canAuthorize() - && $this->getVaultProvider()->getConfigData(static::CAN_AUTHORIZE); + return $this->vaultProvider->canAuthorize() + && $this->vaultProvider->getConfigData(static::CAN_AUTHORIZE); } /** @@ -236,8 +237,8 @@ public function canAuthorize() */ public function canCapture() { - return $this->getVaultProvider()->canCapture() - && $this->getVaultProvider()->getConfigData(static::CAN_CAPTURE); + return $this->vaultProvider->canCapture() + && $this->vaultProvider->getConfigData(static::CAN_CAPTURE); } /** @@ -255,7 +256,7 @@ public function canCapturePartial() */ public function canCaptureOnce() { - return $this->getVaultProvider()->canCaptureOnce(); + return $this->vaultProvider->canCaptureOnce(); } /** @@ -294,7 +295,7 @@ public function canUseInternal() $isInternalAllowed = $this->getConfiguredValue('can_use_internal'); // if config has't been specified for Vault, need to check payment provider option if ($isInternalAllowed === null) { - return $this->getVaultProvider()->canUseInternal(); + return $this->vaultProvider->canUseInternal(); } return (bool) $isInternalAllowed; } @@ -305,7 +306,7 @@ public function canUseInternal() */ public function canUseCheckout() { - return $this->getVaultProvider()->canUseCheckout(); + return $this->vaultProvider->canUseCheckout(); } /** @@ -314,7 +315,7 @@ public function canUseCheckout() */ public function canEdit() { - return $this->getVaultProvider()->canEdit(); + return $this->vaultProvider->canEdit(); } /** @@ -341,7 +342,7 @@ public function fetchTransactionInfo(InfoInterface $payment, $transactionId) */ public function isGateway() { - return $this->getVaultProvider()->isGateway(); + return $this->vaultProvider->isGateway(); } /** @@ -350,7 +351,7 @@ public function isGateway() */ public function isOffline() { - return $this->getVaultProvider()->isOffline(); + return $this->vaultProvider->isOffline(); } /** @@ -359,7 +360,7 @@ public function isOffline() */ public function isInitializeNeeded() { - return $this->getVaultProvider()->isInitializeNeeded(); + return $this->vaultProvider->isInitializeNeeded(); } /** @@ -368,7 +369,7 @@ public function isInitializeNeeded() */ public function canUseForCountry($country) { - return $this->getVaultProvider()->canUseForCountry($country); + return $this->vaultProvider->canUseForCountry($country); } /** @@ -377,7 +378,7 @@ public function canUseForCountry($country) */ public function canUseForCurrency($currencyCode) { - return $this->getVaultProvider()->canUseForCurrency($currencyCode); + return $this->vaultProvider->canUseForCurrency($currencyCode); } /** @@ -386,7 +387,7 @@ public function canUseForCurrency($currencyCode) */ public function getInfoBlockType() { - return $this->getVaultProvider()->getInfoBlockType(); + return $this->vaultProvider->getInfoBlockType(); } /** @@ -395,7 +396,7 @@ public function getInfoBlockType() */ public function getInfoInstance() { - return $this->getVaultProvider()->getInfoInstance(); + return $this->vaultProvider->getInfoInstance(); } /** @@ -404,7 +405,7 @@ public function getInfoInstance() */ public function setInfoInstance(InfoInterface $info) { - $this->getVaultProvider()->setInfoInstance($info); + $this->vaultProvider->setInfoInstance($info); } /** @@ -413,7 +414,7 @@ public function setInfoInstance(InfoInterface $info) */ public function validate() { - return $this->getVaultProvider()->validate(); + return $this->vaultProvider->validate(); } /** @@ -437,9 +438,10 @@ public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount /** @var $payment OrderPaymentInterface */ $this->attachTokenExtensionAttribute($payment); + $this->attachCreditCardInfo($payment); $commandExecutor = $this->commandManagerPool->get( - $this->getVaultProvider()->getCode() + $this->vaultProvider->getCode() ); $commandExecutor->executeByCode( @@ -450,7 +452,7 @@ public function authorize(\Magento\Payment\Model\InfoInterface $payment, $amount ] ); - $payment->setMethod($this->getVaultProvider()->getCode()); + $payment->setMethod($this->vaultProvider->getCode()); return $this; } @@ -473,7 +475,7 @@ public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount) $this->attachTokenExtensionAttribute($payment); $commandExecutor = $this->commandManagerPool->get( - $this->getVaultProvider()->getCode() + $this->vaultProvider->getCode() ); $commandExecutor->executeByCode( @@ -484,10 +486,12 @@ public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount) ] ); - $payment->setMethod($this->getVaultProvider()->getCode()); + $payment->setMethod($this->vaultProvider->getCode()); } /** + * Attaches token extension attribute. + * * @param OrderPaymentInterface $orderPayment * @return void */ @@ -514,6 +518,8 @@ private function attachTokenExtensionAttribute(OrderPaymentInterface $orderPayme } /** + * Returns Payment's extension attributes. + * * @param OrderPaymentInterface $payment * @return \Magento\Sales\Api\Data\OrderPaymentExtensionInterface */ @@ -528,6 +534,20 @@ private function getPaymentExtensionAttributes(OrderPaymentInterface $payment) return $extensionAttributes; } + /** + * Attaches credit card info. + * + * @param OrderPaymentInterface $payment + * @return void + */ + private function attachCreditCardInfo(OrderPaymentInterface $payment): void + { + $paymentToken = $payment->getExtensionAttributes() + ->getVaultPaymentToken(); + $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); + $payment->addData($tokenDetails); + } + /** * @inheritdoc * @since 100.1.0 @@ -615,7 +635,7 @@ public function assignData(\Magento\Framework\DataObject $data) ] ); - return $this->getVaultProvider()->assignData($data); + return $this->vaultProvider->assignData($data); } /** @@ -624,7 +644,7 @@ public function assignData(\Magento\Framework\DataObject $data) */ public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null) { - return $this->getVaultProvider()->isAvailable($quote) + return $this->vaultProvider->isAvailable($quote) && $this->config->getValue(self::$activeKey, $this->getStore() ?: ($quote ? $quote->getStoreId() : null)); } @@ -634,7 +654,7 @@ public function isAvailable(\Magento\Quote\Api\Data\CartInterface $quote = null) */ public function isActive($storeId = null) { - return $this->getVaultProvider()->isActive($storeId) + return $this->vaultProvider->isActive($storeId) && $this->config->getValue(self::$activeKey, $this->getStore() ?: $storeId); } @@ -653,7 +673,7 @@ public function initialize($paymentAction, $stateObject) */ public function getConfigPaymentAction() { - return $this->getVaultProvider()->getConfigPaymentAction(); + return $this->vaultProvider->getConfigPaymentAction(); } /** @@ -662,6 +682,6 @@ public function getConfigPaymentAction() */ public function getProviderCode() { - return $this->getVaultProvider()->getCode(); + return $this->vaultProvider->getCode(); } } diff --git a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php index 7e3f9b67a58ec..ae6f35822276f 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php @@ -23,6 +23,7 @@ use Magento\Vault\Api\PaymentTokenManagementInterface; use Magento\Vault\Model\Method\Vault; use Magento\Vault\Model\VaultPaymentInterface; +use Magento\Framework\Serialize\Serializer\Json; use PHPUnit_Framework_MockObject_MockObject as MockObject; /** @@ -42,10 +43,19 @@ class VaultTest extends \PHPUnit\Framework\TestCase */ private $vaultProvider; + /** + * @var Json|MockObject + */ + private $jsonSerializer; + + /** + * @inheritdoc + */ public function setUp() { $this->objectManager = new ObjectManager($this); $this->vaultProvider = $this->createMock(MethodInterface::class); + $this->jsonSerializer = $this->createMock(Json::class); } /** @@ -152,6 +162,19 @@ public function testAuthorize() $tokenManagement = $this->createMock(PaymentTokenManagementInterface::class); $token = $this->createMock(PaymentTokenInterface::class); + $tokenDetails = [ + 'cc_last4' => '1111', + 'cc_type' => 'VI', + 'cc_exp_year' => '2020', + 'cc_exp_month' => '01', + ]; + + $extensionAttributes->method('getVaultPaymentToken') + ->willReturn($token); + + $this->jsonSerializer->method('unserialize') + ->willReturn($tokenDetails); + $paymentModel->expects(static::once()) ->method('getAdditionalInformation') ->willReturn( @@ -164,8 +187,7 @@ public function testAuthorize() ->method('getByPublicHash') ->with($publicHash, $customerId) ->willReturn($token); - $paymentModel->expects(static::once()) - ->method('getExtensionAttributes') + $paymentModel->method('getExtensionAttributes') ->willReturn($extensionAttributes); $extensionAttributes->expects(static::once()) ->method('setVaultPaymentToken') @@ -198,7 +220,8 @@ public function testAuthorize() [ 'tokenManagement' => $tokenManagement, 'commandManagerPool' => $commandManagerPool, - 'vaultProvider' => $this->vaultProvider + 'vaultProvider' => $this->vaultProvider, + 'jsonSerializer' => $this->jsonSerializer, ] ); $model->authorize($paymentModel, $amount); diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml index 7c646d2c05cc6..fe2ff170b0e0a 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml @@ -24,9 +24,14 @@ <data name="configData" xsi:type="string">payflowpro, payflowpro_use_vault</data> <data name="status" xsi:type="string">Processing</data> <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> + <data name="paymentInfo" xsi:type="array"> + <item name="Credit Card Type" xsi:type="string">Visa</item> + <item name="Credit Card Number" xsi:type="string">xxxx-1111</item> + </data> <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderPaymentInformation" /> </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/SubmitOrderStep.php b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/SubmitOrderStep.php index 0ba457fd3b668..c9c6a00829a5c 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/SubmitOrderStep.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestStep/SubmitOrderStep.php @@ -114,7 +114,7 @@ public function run() $orderData = $this->order !== null ? $this->order->getData() : []; $order = $this->fixtureFactory->createByCode( 'orderInjectable', - ['data' => array_merge($data, $orderData)] + ['data' => array_merge($orderData, $data)] ); return ['orderId' => $orderId, 'order' => $order]; diff --git a/dev/tests/integration/testsuite/Magento/InstantPurchase/_files/fake_payment_token.php b/dev/tests/integration/testsuite/Magento/InstantPurchase/_files/fake_payment_token.php index ad2fc8f836168..276fb77a22ea2 100644 --- a/dev/tests/integration/testsuite/Magento/InstantPurchase/_files/fake_payment_token.php +++ b/dev/tests/integration/testsuite/Magento/InstantPurchase/_files/fake_payment_token.php @@ -19,4 +19,6 @@ $token->setIsVisible(true); $token->setCreatedAt(strtotime('-1 day')); $token->setExpiresAt(strtotime('+1 day')); +$tokenDetails = ['cc_last4' => '1111', 'cc_exp_year' => '2020', 'cc_exp_month' => '01', 'cc_type' => 'VI']; +$token->setTokenDetails(json_encode($tokenDetails)); $repository->save($token); From 486d3833033b9992ecefabd09b698a776f5efe72 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Thu, 8 Aug 2019 19:08:38 +0200 Subject: [PATCH 173/841] Moved old acceptance test --- .../CustomerDownloadableProductTest.php | 189 +++++++----------- .../CustomerDownloadableProductsTest.php | 118 ----------- 2 files changed, 75 insertions(+), 232 deletions(-) delete mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Downloadable/CustomerDownloadableProductsTest.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php index 77de8aa3917fb..6b8aad83edac7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php @@ -7,139 +7,100 @@ namespace Magento\GraphQl\CustomerDownloadableProduct; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\TestFramework\ObjectManager; -use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; -use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Sales\Api\OrderRepositoryInterface; -use Magento\Framework\Api\SearchCriteriaBuilder; +/** + * Test retrieving of customer downloadable products. + */ class CustomerDownloadableProductTest extends GraphQlAbstract { - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php - * @magentoApiDataFixture Magento/Downloadable/_files/order_with_downloadable_product.php + * @magentoApiDataFixture Magento/Downloadable/_files/customer_order_with_downloadable_product.php */ - public function testGetCustomerDownloadableProducts() + public function testCustomerDownloadableProducts() { - $query = <<<MUTATION -mutation { - generateCustomerToken( - email: "customer@example.com" - password: "password" - ) { - token - } -} -MUTATION; - $response = $this->graphQlMutation($query); - $token = $response['generateCustomerToken']['token']; - $this->headers = ['Authorization' => 'Bearer ' . $token]; - - $query = <<<QUERY - { - customerDownloadableProducts{ - items{ - order_increment_id - date - status - download_url - remaining_downloads - } + $query = $this->getQuery(); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('items', $response['customerDownloadableProducts']); + self::assertCount(1, $response['customerDownloadableProducts']['items']); + self::assertArrayHasKey('date', $response['customerDownloadableProducts']['items'][0]); + self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['date']); + self::assertArrayHasKey('download_url', $response['customerDownloadableProducts']['items'][0]); + self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['download_url']); + self::assertArrayHasKey('order_increment_id', $response['customerDownloadableProducts']['items'][0]); + self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['order_increment_id']); + self::assertArrayHasKey('remaining_downloads', $response['customerDownloadableProducts']['items'][0]); + self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['remaining_downloads']); + self::assertArrayHasKey('status', $response['customerDownloadableProducts']['items'][0]); + self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['status']); } - -} -QUERY; - $objectManager = ObjectManager::getInstance(); - - $searchCriteria = $objectManager->get(SearchCriteriaBuilder::class)->create(); - - $orderRepository = $objectManager->create(OrderRepositoryInterface::class); - $orders = $orderRepository->getList($searchCriteria)->getItems(); - $order = array_pop($orders); - - $searchCriteria = $objectManager->get( - SearchCriteriaBuilder::class - )->addFilter( - 'email', - 'customer@example.com' - )->create(); - - $customerRepository = $objectManager->create(CustomerRepositoryInterface::class); - $customers = $customerRepository->getList($searchCriteria) - ->getItems(); - $customer = array_pop($customers); - - $order->setCustomerId($customer->getId())->setCustomerIsGuest(false)->save(); - $response = $this->graphQlQuery($query, [], '', $this->headers); - - $this->assertEquals( - $order->getIncrementId(), - $response['customerDownloadableProducts']['items'][0]['order_increment_id'] - ); - } - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php + * @magentoApiDataFixture Magento/Downloadable/_files/customer_order_with_downloadable_product.php + * + * @expectedException \Exception + * @expectedExceptionMessage The current customer isn't authorized. */ - public function testGetCustomerDownloadableProductsIfProductsDoNotExist() + public function testGuestCannotAccessDownloadableProducts() { - $query = <<<MUTATION -mutation { - generateCustomerToken( - email: "customer@example.com" - password: "password" - ) { - token - } -} -MUTATION; - $response = $this->graphQlMutation($query); - $token = $response['generateCustomerToken']['token']; - $this->headers = ['Authorization' => 'Bearer ' . $token]; - - $query = <<<QUERY - { - customerDownloadableProducts{ - items{ - order_increment_id - date - status - download_url - remaining_downloads - } + $this->graphQlQuery($this->getQuery()); } - -} -QUERY; - - $response = $this->graphQlQuery($query, [], '', $this->headers); - $this->assertEmpty($response['customerDownloadableProducts']['items']); + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testCustomerHasNoOrders() + { + $query = $this->getQuery(); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + self::assertArrayHasKey('items', $response['customerDownloadableProducts']); + self::assertCount(0, $response['customerDownloadableProducts']['items']); } - - public function testGuestCannotAccessDownloadableProducts() + /** + * @return string + */ + private function getQuery(): string { - $query = <<<QUERY - { - customerDownloadableProducts{ - items{ - order_increment_id - date - status - download_url - remaining_downloads - } + return <<<QUERY +{ + customerDownloadableProducts { + items { + date + download_url + order_increment_id + remaining_downloads + status } - + } } QUERY; - - $this->expectException(ResponseContainsErrorsException::class); - $this->expectExceptionMessage('GraphQL response contains errors: The current customer isn\'t authorized'); - $this->graphQlQuery($query); + } + /** + * @param string $username + * @param string $password + * @return array + * @throws \Magento\Framework\Exception\AuthenticationException + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Downloadable/CustomerDownloadableProductsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Downloadable/CustomerDownloadableProductsTest.php deleted file mode 100644 index 47fd8d66b92f1..0000000000000 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Downloadable/CustomerDownloadableProductsTest.php +++ /dev/null @@ -1,118 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\GraphQl\Downloadable; - -use Magento\Integration\Api\CustomerTokenServiceInterface; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\TestCase\GraphQlAbstract; - -/** - * Test retrieving of customer downloadable products. - */ -class CustomerDownloadableProductsTest extends GraphQlAbstract -{ - /** - * @var CustomerTokenServiceInterface - */ - private $customerTokenService; - - /** - * @inheritdoc - */ - protected function setUp() - { - $objectManager = Bootstrap::getObjectManager(); - $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); - } - - /** - * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php - * @magentoApiDataFixture Magento/Downloadable/_files/customer_order_with_downloadable_product.php - */ - public function testCustomerDownloadableProducts() - { - $query = $this->getQuery(); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - - self::assertArrayHasKey('items', $response['customerDownloadableProducts']); - self::assertCount(1, $response['customerDownloadableProducts']['items']); - - self::assertArrayHasKey('date', $response['customerDownloadableProducts']['items'][0]); - self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['date']); - - self::assertArrayHasKey('download_url', $response['customerDownloadableProducts']['items'][0]); - self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['download_url']); - - self::assertArrayHasKey('order_increment_id', $response['customerDownloadableProducts']['items'][0]); - self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['order_increment_id']); - - self::assertArrayHasKey('remaining_downloads', $response['customerDownloadableProducts']['items'][0]); - self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['remaining_downloads']); - - self::assertArrayHasKey('status', $response['customerDownloadableProducts']['items'][0]); - self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['status']); - } - - /** - * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php - * @magentoApiDataFixture Magento/Downloadable/_files/customer_order_with_downloadable_product.php - * - * @expectedException \Exception - * @expectedExceptionMessage The current customer isn't authorized. - */ - public function testGuestCannotAccessDownloadableProducts() - { - $this->graphQlQuery($this->getQuery()); - } - - /** - * @magentoApiDataFixture Magento/Customer/_files/customer.php - */ - public function testCustomerHasNoOrders() - { - $query = $this->getQuery(); - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - - self::assertArrayHasKey('items', $response['customerDownloadableProducts']); - self::assertCount(0, $response['customerDownloadableProducts']['items']); - } - - /** - * @return string - */ - private function getQuery(): string - { - return <<<QUERY -{ - customerDownloadableProducts { - items { - date - download_url - order_increment_id - remaining_downloads - status - } - } -} -QUERY; - } - - /** - * @param string $username - * @param string $password - * @return array - * @throws \Magento\Framework\Exception\AuthenticationException - */ - private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array - { - $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); - return ['Authorization' => 'Bearer ' . $customerToken]; - } -} From 7170102b50c589b4bc92ca4125afd906dcf4e559 Mon Sep 17 00:00:00 2001 From: arushibansal013 <53007383+arushibansal013@users.noreply.github.com> Date: Fri, 9 Aug 2019 00:45:01 +0530 Subject: [PATCH 174/841] Added startPath for bundle product price Added start path for bundle product price display issue, Since similar path get formed after alerts are enabled. --- .../Ui/DataProvider/Product/Form/Modifier/BundlePrice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php index f431012dc3fa5..6ddb539b3c254 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php @@ -64,7 +64,7 @@ public function modifyMeta(array $meta) $this->arrayManager->findPath( ProductAttributeInterface::CODE_PRICE, $meta, - null, + 'product-details/children', 'children' ) . static::META_CONFIG_PATH, $meta, From 87732f34eb8ee030147c7f12d6ee66f3044493a1 Mon Sep 17 00:00:00 2001 From: arushibansal013 <53007383+arushibansal013@users.noreply.github.com> Date: Fri, 9 Aug 2019 12:50:48 +0530 Subject: [PATCH 175/841] Fixed inherit doc and fetched parent-details from constant Removed inherit doc as per magento documentation as well as changed parent-details to fetch from constant --- .../DataProvider/Product/Form/Modifier/BundlePrice.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php index 6ddb539b3c254..7f79c76dbc9a9 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php @@ -39,9 +39,10 @@ public function __construct( $this->locator = $locator; $this->arrayManager = $arrayManager; } - + /** - * {@inheritdoc} + * @param array $meta + * @return array */ public function modifyMeta(array $meta) { @@ -64,7 +65,7 @@ public function modifyMeta(array $meta) $this->arrayManager->findPath( ProductAttributeInterface::CODE_PRICE, $meta, - 'product-details/children', + self::DEFAULT_GENERAL_PANEL.'/children', 'children' ) . static::META_CONFIG_PATH, $meta, @@ -94,7 +95,8 @@ public function modifyMeta(array $meta) } /** - * {@inheritdoc} + * @param array $data + * @return array */ public function modifyData(array $data) { From 375efd1aeaa492639a7c6ba0efc64b750433fc70 Mon Sep 17 00:00:00 2001 From: arushibansal013 <53007383+arushibansal013@users.noreply.github.com> Date: Fri, 9 Aug 2019 13:45:31 +0530 Subject: [PATCH 176/841] Added short description to comments --- .../Ui/DataProvider/Product/Form/Modifier/BundlePrice.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php index 7f79c76dbc9a9..58785b96687db 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php @@ -41,6 +41,8 @@ public function __construct( } /** + * modified modify meta for bundle product + * * @param array $meta * @return array */ @@ -95,6 +97,8 @@ public function modifyMeta(array $meta) } /** + * modify data for bundle product + * * @param array $data * @return array */ From d62890f48a8289027d792ef941f4a020abfa2f2f Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 9 Aug 2019 11:26:11 +0300 Subject: [PATCH 177/841] MC-18561: After changing store view the cms page is not redirecting correctly --- .../Plugin/Cms/Model/Store/View.php | 121 ++++++++++++++++++ app/code/Magento/CmsUrlRewrite/etc/di.xml | 3 + .../Plugin/Cms/Model/Store/ViewTest.php | 47 +++++++ 3 files changed, 171 insertions(+) create mode 100644 app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php create mode 100644 dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php new file mode 100644 index 0000000000000..a5d8ad7b1fa85 --- /dev/null +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CmsUrlRewrite\Plugin\Cms\Model\Store; + +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\CmsUrlRewrite\Model\CmsPageUrlRewriteGenerator; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Model\AbstractModel; +use Magento\Store\Model\ResourceModel\Store; +use Magento\UrlRewrite\Model\UrlPersistInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; + +/** + * Plugin which is listening store resource model and on save replace cms page url rewrites + * + * @see Store + */ +class View +{ + /** + * @var AbstractModel + */ + private $origStore; + + /** + * @var UrlPersistInterface + */ + private $urlPersist; + + /** + * @var CmsPageUrlRewriteGenerator + */ + private $cmsPageUrlRewriteGenerator; + + /** + * @var PageRepositoryInterface + */ + private $pageRepository; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + + /** + * @param UrlPersistInterface $urlPersist + * @param SearchCriteriaBuilder $searchCriteriaBuilder + * @param PageRepositoryInterface $pageRepository + * @param CmsPageUrlRewriteGenerator $cmsPageUrlRewriteGenerator + */ + public function __construct( + UrlPersistInterface $urlPersist, + SearchCriteriaBuilder $searchCriteriaBuilder, + PageRepositoryInterface $pageRepository, + CmsPageUrlRewriteGenerator $cmsPageUrlRewriteGenerator + ) { + $this->urlPersist = $urlPersist; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->pageRepository = $pageRepository; + $this->cmsPageUrlRewriteGenerator = $cmsPageUrlRewriteGenerator; + } + + /** + * @param Store $object + * @param AbstractModel $store + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSave( + Store $object, + AbstractModel $store + ) { + $this->origStore = $store; + } + + /** + * Regenerate urls on store after save + * + * @param Store $store + * @return Store + */ + public function afterSave( + Store $store + ) { + if ($this->origStore->isObjectNew() || $this->origStore->dataHasChangedFor('group_id')) { + if (!$this->origStore->isObjectNew()) { + $this->urlPersist->deleteByData([UrlRewrite::STORE_ID => $this->origStore->getId()]); + } + $this->urlPersist->replace( + $this->generateCmsPagesUrls($this->origStore->getId()) + ); + } + return $store; + } + + /** + * Generate url rewrites for cms pages to store view + * + * @param int $storeId + * @return array + */ + private function generateCmsPagesUrls($storeId): array + { + $urls = []; + $searchCriteria = $this->searchCriteriaBuilder->create(); + $cmsPagesCollection = $this->pageRepository->getList($searchCriteria)->getItems(); + foreach ($cmsPagesCollection as $page) { + $page->setStoreId($storeId); + /** @var \Magento\Cms\Model\Page $page */ + $urls = array_merge( + $urls, + $this->cmsPageUrlRewriteGenerator->generate($page) + ); + } + return $urls; + } +} diff --git a/app/code/Magento/CmsUrlRewrite/etc/di.xml b/app/code/Magento/CmsUrlRewrite/etc/di.xml index 497d7a175842d..d39d2e729707b 100644 --- a/app/code/Magento/CmsUrlRewrite/etc/di.xml +++ b/app/code/Magento/CmsUrlRewrite/etc/di.xml @@ -9,4 +9,7 @@ <type name="Magento\Cms\Model\ResourceModel\Page"> <plugin name="cms_url_rewrite_plugin" type="Magento\CmsUrlRewrite\Plugin\Cms\Model\ResourceModel\Page"/> </type> + <type name="Magento\Store\Model\ResourceModel\Store"> + <plugin name="cms_url_rewrite_after_store_save" type="Magento\CmsUrlRewrite\Plugin\Cms\Model\Store\View"/> + </type> </config> diff --git a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php new file mode 100644 index 0000000000000..923c4d38dd95d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php @@ -0,0 +1,47 @@ +<?php + +namespace Magento\CmsUrlRewrite\Plugin\Cms\Model\Store; + +use Magento\Store\Model\StoreFactory; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; + +/** + * Test for plugin which is listening store resource model and on save replace cms page url rewrites + */ +class ViewTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var UrlFinderInterface + */ + private $urlFinder; + + /** + * @var StoreFactory + */ + private $storeFactory; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->storeFactory = Bootstrap::getObjectManager()->create(StoreFactory::class); + $this->urlFinder = Bootstrap::getObjectManager()->create(UrlFinderInterface::class); + } + + /** + * @magentoDataFixture Magento/Cms/_files/pages.php + * @magentoDataFixture Magento/Store/_files/store.php + * @magentoAppArea adminhtml + */ + public function testAfterSave() + { + $data = [ + UrlRewrite::REQUEST_PATH => 'page100', + ]; + $urlRewrites = $this->urlFinder->findAllByData($data); + $this->assertCount(2, $urlRewrites); + } +} From 035800e69413d66df8ae901f3ab244c46ee51706 Mon Sep 17 00:00:00 2001 From: arushibansal013 <53007383+arushibansal013@users.noreply.github.com> Date: Fri, 9 Aug 2019 14:15:22 +0530 Subject: [PATCH 178/841] Capitialize the short description --- .../Ui/DataProvider/Product/Form/Modifier/BundlePrice.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php index 58785b96687db..20f0258cb7a36 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php @@ -41,7 +41,7 @@ public function __construct( } /** - * modified modify meta for bundle product + * Modified modify meta for bundle product * * @param array $meta * @return array @@ -97,7 +97,7 @@ public function modifyMeta(array $meta) } /** - * modify data for bundle product + * Modify data for bundle product * * @param array $data * @return array From d7a9a4fb4612ebc2e35b7adc60db1d6c3d85bc51 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 9 Aug 2019 13:57:02 +0300 Subject: [PATCH 179/841] MC-18561: After changing store view the cms page is not redirecting correctly --- .../Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php | 2 ++ .../Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index a5d8ad7b1fa85..c8961dfc7e6b2 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -65,6 +65,8 @@ public function __construct( } /** + * Get the correct store for later regenerate url + * * @param Store $object * @param AbstractModel $store * @return void diff --git a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php index 923c4d38dd95d..39b6a0335022a 100644 --- a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php +++ b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php @@ -1,4 +1,8 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ namespace Magento\CmsUrlRewrite\Plugin\Cms\Model\Store; From 308162eaf4d1b5a108d70aad741558dcfccde43e Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 9 Aug 2019 15:25:48 +0300 Subject: [PATCH 180/841] MC-18561: After changing store view the cms page is not redirecting correctly --- app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index c8961dfc7e6b2..789027228aaa1 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -113,6 +113,7 @@ private function generateCmsPagesUrls($storeId): array foreach ($cmsPagesCollection as $page) { $page->setStoreId($storeId); /** @var \Magento\Cms\Model\Page $page */ + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $urls = array_merge( $urls, $this->cmsPageUrlRewriteGenerator->generate($page) From b287f41a7653945cf336326d9c486c7e71ab0ae0 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 9 Aug 2019 17:41:15 +0300 Subject: [PATCH 181/841] MC-18561: After changing store view the cms page is not redirecting correctly --- .../Plugin/Cms/Model/Store/View.php | 64 ++++-------- .../CmsUrlRewrite/etc/adminhtml/di.xml | 12 +++ app/code/Magento/CmsUrlRewrite/etc/di.xml | 3 - .../Plugin/Cms/Model/Store/ViewTest.php | 97 +++++++++++++++++-- 4 files changed, 124 insertions(+), 52 deletions(-) create mode 100644 app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index 789027228aaa1..1ab6c0d568db0 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -3,29 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CmsUrlRewrite\Plugin\Cms\Model\Store; use Magento\Cms\Api\PageRepositoryInterface; use Magento\CmsUrlRewrite\Model\CmsPageUrlRewriteGenerator; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Framework\Model\AbstractModel; -use Magento\Store\Model\ResourceModel\Store; +use Magento\Store\Model\Store; +use Magento\Store\Model\ResourceModel\Store as ResourceStore; use Magento\UrlRewrite\Model\UrlPersistInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; /** * Plugin which is listening store resource model and on save replace cms page url rewrites * - * @see Store + * @see ResourceStore */ class View { - /** - * @var AbstractModel - */ - private $origStore; - /** * @var UrlPersistInterface */ @@ -65,38 +60,23 @@ public function __construct( } /** - * Get the correct store for later regenerate url - * - * @param Store $object - * @param AbstractModel $store - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSave( - Store $object, - AbstractModel $store - ) { - $this->origStore = $store; - } - - /** - * Regenerate urls on store after save - * + * @param ResourceStore $object + * @param \Closure $proceed * @param Store $store - * @return Store + * @return mixed + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterSave( - Store $store - ) { - if ($this->origStore->isObjectNew() || $this->origStore->dataHasChangedFor('group_id')) { - if (!$this->origStore->isObjectNew()) { - $this->urlPersist->deleteByData([UrlRewrite::STORE_ID => $this->origStore->getId()]); - } + public function aroundSave(ResourceStore $object, \Closure $proceed, Store $store) + { + $newStore = $store->isObjectNew() || $store->dataHasChangedFor('group_id'); + $result = $proceed($store); + if ($newStore) { $this->urlPersist->replace( - $this->generateCmsPagesUrls($this->origStore->getId()) + $this->generateCmsPagesUrls((int)$store->getId()) ); } - return $store; + + return $result; } /** @@ -105,20 +85,18 @@ public function afterSave( * @param int $storeId * @return array */ - private function generateCmsPagesUrls($storeId): array + private function generateCmsPagesUrls(int $storeId): array { + $rewrites = []; $urls = []; $searchCriteria = $this->searchCriteriaBuilder->create(); $cmsPagesCollection = $this->pageRepository->getList($searchCriteria)->getItems(); foreach ($cmsPagesCollection as $page) { $page->setStoreId($storeId); - /** @var \Magento\Cms\Model\Page $page */ - // phpcs:ignore Magento2.Performance.ForeachArrayMerge - $urls = array_merge( - $urls, - $this->cmsPageUrlRewriteGenerator->generate($page) - ); + $rewrites[] = $this->cmsPageUrlRewriteGenerator->generate($page); } + $urls = array_merge($urls, ...$rewrites); + return $urls; } } diff --git a/app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml b/app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml new file mode 100644 index 0000000000000..5e4de51808cbd --- /dev/null +++ b/app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Store\Model\ResourceModel\Store"> + <plugin name="cms_url_rewrite_after_store_save" type="Magento\CmsUrlRewrite\Plugin\Cms\Model\Store\View"/> + </type> +</config> diff --git a/app/code/Magento/CmsUrlRewrite/etc/di.xml b/app/code/Magento/CmsUrlRewrite/etc/di.xml index d39d2e729707b..497d7a175842d 100644 --- a/app/code/Magento/CmsUrlRewrite/etc/di.xml +++ b/app/code/Magento/CmsUrlRewrite/etc/di.xml @@ -9,7 +9,4 @@ <type name="Magento\Cms\Model\ResourceModel\Page"> <plugin name="cms_url_rewrite_plugin" type="Magento\CmsUrlRewrite\Plugin\Cms\Model\ResourceModel\Page"/> </type> - <type name="Magento\Store\Model\ResourceModel\Store"> - <plugin name="cms_url_rewrite_after_store_save" type="Magento\CmsUrlRewrite\Plugin\Cms\Model\Store\View"/> - </type> </config> diff --git a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php index 39b6a0335022a..ea7c23b05605d 100644 --- a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php +++ b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\CmsUrlRewrite\Plugin\Cms\Model\Store; -use Magento\Store\Model\StoreFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Registry; +use Magento\Store\Model\Store; use Magento\TestFramework\Helper\Bootstrap; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -22,22 +25,23 @@ class ViewTest extends \PHPUnit\Framework\TestCase private $urlFinder; /** - * @var StoreFactory + * @var ObjectManagerInterface */ - private $storeFactory; + private $objectManager; /** * @inheritdoc */ protected function setUp() { - $this->storeFactory = Bootstrap::getObjectManager()->create(StoreFactory::class); - $this->urlFinder = Bootstrap::getObjectManager()->create(UrlFinderInterface::class); + $this->objectManager = Bootstrap::getObjectManager(); + $this->urlFinder = $this->objectManager->create(UrlFinderInterface::class); } /** + * Test of replacing cms page url rewrites on create and delete store + * * @magentoDataFixture Magento/Cms/_files/pages.php - * @magentoDataFixture Magento/Store/_files/store.php * @magentoAppArea adminhtml */ public function testAfterSave() @@ -46,6 +50,87 @@ public function testAfterSave() UrlRewrite::REQUEST_PATH => 'page100', ]; $urlRewrites = $this->urlFinder->findAllByData($data); + $this->assertCount(1, $urlRewrites); + $this->createStore(); + $urlRewrites = $this->urlFinder->findAllByData($data); $this->assertCount(2, $urlRewrites); + $this->deleteStore(); + $urlRewrites = $this->urlFinder->findAllByData($data); + $this->assertCount(1, $urlRewrites); + } + + /** + * Create test store + * + * @return void + */ + private function createStore(): void + { + /** @var $store Store */ + $store = $this->objectManager->create(Store::class); + if (!$store->load('test', 'code')->getId()) { + $store->setData( + [ + 'code' => 'test', + 'website_id' => '1', + 'group_id' => '1', + 'name' => 'Test Store', + 'sort_order' => '0', + 'is_active' => '1', + ] + ); + $store->save(); + } else { + if ($store->getId()) { + /** @var \Magento\TestFramework\Helper\Bootstrap $registry */ + $registry = Bootstrap::getObjectManager()->get( + Registry::class + ); + $registry->unregister('isSecureArea'); + $registry->register('isSecureArea', true); + $store->delete(); + $registry->unregister('isSecureArea'); + $registry->register('isSecureArea', false); + $store = $this->objectManager->create(Store::class); + $store->setData( + [ + 'code' => 'test', + 'website_id' => '1', + 'group_id' => '1', + 'name' => 'Test Store', + 'sort_order' => '0', + 'is_active' => '1', + ] + ); + $store->save(); + } + } + } + + /** + * Delete test store + * + * @return void + */ + private function deleteStore(): void + { + /** @var Registry $registry */ + $registry = $this->objectManager->get(Registry::class); + $registry->unregister('isSecureArea'); + $registry->register('isSecureArea', true); + /** @var Store $store */ + $store = $this->objectManager->get(Store::class); + $store->load('test', 'code'); + if ($store->getId()) { + $store->delete(); + } + /** @var Store $store */ + $store = $this->objectManager->get(Store::class); + $store->load('test', 'code'); + if ($store->getId()) { + $store->delete(); + } + $registry->unregister('isSecureArea'); + $registry->register('isSecureArea', false); } } From b82bdda674f7a222f7a471ff722015dbcf74f9af Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 9 Aug 2019 17:55:23 +0300 Subject: [PATCH 182/841] MC-18561: After changing store view the cms page is not redirecting correctly --- app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index 1ab6c0d568db0..308a465b7de90 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -68,9 +68,8 @@ public function __construct( */ public function aroundSave(ResourceStore $object, \Closure $proceed, Store $store) { - $newStore = $store->isObjectNew() || $store->dataHasChangedFor('group_id'); $result = $proceed($store); - if ($newStore) { + if ($store->isObjectNew() || $store->dataHasChangedFor('group_id')) { $this->urlPersist->replace( $this->generateCmsPagesUrls((int)$store->getId()) ); From e085e1b141e4b70e1098b027e5d5e2929f38ba35 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Fri, 9 Aug 2019 12:37:11 -0500 Subject: [PATCH 183/841] MC-19064: Fix CookieAndSessionMisuse static test --- .../Rule/Design/CookieAndSessionMisuse.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php index 707c3442d4056..1d49d9343a282 100644 --- a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php @@ -16,6 +16,8 @@ /** * Session and Cookies must be used only in HTML Presentation layer. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CookieAndSessionMisuse extends AbstractRule implements ClassAware { @@ -67,6 +69,19 @@ private function isLayoutProcessor(\ReflectionClass $class): bool ); } + /** + * Is given class a View Model? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isViewModel(\ReflectionClass $class): bool + { + return $class->isSubclassOf( + \Magento\Framework\View\Element\Block\ArgumentInterface::class + ); + } + /** * Is given class an HTML UI Document? * @@ -191,6 +206,7 @@ public function apply(AbstractNode $node) && !$this->isControllerPlugin($class) && !$this->isBlockPlugin($class) && !$this->isLayoutProcessor($class) + && !$this->isViewModel($class) ) { $this->addViolation($node, [$node->getFullQualifiedName()]); } From 7d3d37cb3b011f4eddb123ed50eef08520fba167 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Fri, 9 Aug 2019 14:22:59 -0500 Subject: [PATCH 184/841] MC-18833: Tier price API doesn't take into account indexers mode --- app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php index 4a5ea36a68a61..c75a3541ba9c3 100644 --- a/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php +++ b/app/code/Magento/Indexer/Model/Indexer/CacheCleaner.php @@ -90,6 +90,8 @@ public function afterExecuteRow(ActionInterface $subject) */ private function cleanCache() { + $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); + $identities = $this->cacheContext->getIdentities(); if (!empty($identities)) { $this->appCache->clean($identities); From 62dd8f51c6fb2217ed8af94547a9b81dcdd908c1 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 6 Aug 2019 10:40:39 -0500 Subject: [PATCH 185/841] Add custom attributes to products filter and sort input --- .../Model/Config/FilterAttributeReader.php | 98 +++++++++++++++++++ .../Model/Config/SortAttributeReader.php | 79 +++++++++++++++ app/code/Magento/CatalogGraphQl/etc/di.xml | 2 + .../Magento/CatalogGraphQl/etc/graphql/di.xml | 6 ++ 4 files changed, 185 insertions(+) create mode 100644 app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php new file mode 100644 index 0000000000000..b84c18ea5ac0b --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Config; + +use Magento\Framework\Config\ReaderInterface; +use Magento\Framework\GraphQl\Schema\Type\Entity\MapperInterface; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection; + +/** + * Adds custom/eav attributes to product filter type in the GraphQL config. + * + * Product Attribute should satisfy the following criteria: + * - Attribute is searchable + * - "Visible in Advanced Search" is set to "Yes" + * - Attribute of type "Select" must have options + */ +class FilterAttributeReader implements ReaderInterface +{ + /** + * Entity type constant + */ + private const ENTITY_TYPE = 'filter_attributes'; + + /** + * Filter input types + */ + private const FILTER_TYPE = 'FilterTypeInput'; + + /** + * @var MapperInterface + */ + private $mapper; + + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @param MapperInterface $mapper + * @param CollectionFactory $collectionFactory + */ + public function __construct( + MapperInterface $mapper, + CollectionFactory $collectionFactory + ) { + $this->mapper = $mapper; + $this->collectionFactory = $collectionFactory; + } + + /** + * Read configuration scope + * + * @param string|null $scope + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function read($scope = null) : array + { + $typeNames = $this->mapper->getMappedTypes(self::ENTITY_TYPE); + $config = []; + + foreach ($this->getAttributeCollection() as $attribute) { + $attributeCode = $attribute->getAttributeCode(); + + foreach ($typeNames as $typeName) { + $config[$typeName]['fields'][$attributeCode] = [ + 'name' => $attributeCode, + 'type' => self::FILTER_TYPE, + 'arguments' => [], + 'required' => false, + 'description' => sprintf('Attribute label: %s', $attribute->getDefaultFrontendLabel()) + ]; + } + } + + return $config; + } + + /** + * Create attribute collection + * + * @return Collection|\Magento\Catalog\Model\ResourceModel\Eav\Attribute[] + */ + private function getAttributeCollection() + { + return $this->collectionFactory->create() + ->addHasOptionsFilter() + ->addIsSearchableFilter() + ->addDisplayInAdvancedSearchFilter(); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php new file mode 100644 index 0000000000000..215b28be0579c --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogGraphQl\Model\Config; + +use Magento\Framework\Config\ReaderInterface; +use Magento\Framework\GraphQl\Schema\Type\Entity\MapperInterface; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection as AttributesCollection; + +/** + * Adds custom/eav attribute to catalog products sorting in the GraphQL config. + */ +class SortAttributeReader implements ReaderInterface +{ + /** + * Entity type constant + */ + private const ENTITY_TYPE = 'sort_attributes'; + + /** + * Fields type constant + */ + private const FIELD_TYPE = 'SortEnum'; + + /** + * @var MapperInterface + */ + private $mapper; + + /** + * @var AttributesCollection + */ + private $attributesCollection; + + /** + * @param MapperInterface $mapper + * @param AttributesCollection $attributesCollection + */ + public function __construct( + MapperInterface $mapper, + AttributesCollection $attributesCollection + ) { + $this->mapper = $mapper; + $this->attributesCollection = $attributesCollection; + } + + /** + * Read configuration scope + * + * @param string|null $scope + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function read($scope = null) : array + { + $map = $this->mapper->getMappedTypes(self::ENTITY_TYPE); + $config =[]; + $attributes = $this->attributesCollection->addSearchableAttributeFilter()->addFilter('used_for_sort_by', 1); + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ + foreach ($attributes as $attribute) { + $attributeCode = $attribute->getAttributeCode(); + $attributeLabel = $attribute->getDefaultFrontendLabel(); + foreach ($map as $type) { + $config[$type]['fields'][$attributeCode] = [ + 'name' => $attributeCode, + 'type' => self::FIELD_TYPE, + 'arguments' => [], + 'required' => false, + 'description' => __('Attribute label: ') . $attributeLabel + ]; + } + } + + return $config; + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml index a5006355ed265..cea1c7ce327e2 100644 --- a/app/code/Magento/CatalogGraphQl/etc/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/di.xml @@ -19,6 +19,8 @@ <argument name="readers" xsi:type="array"> <item name="productDynamicAttributeReader" xsi:type="object">Magento\CatalogGraphQl\Model\Config\AttributeReader</item> <item name="categoryDynamicAttributeReader" xsi:type="object">Magento\CatalogGraphQl\Model\Config\CategoryAttributeReader</item> + <item name="productSortDynamicAttributeReader" xsi:type="object">Magento\CatalogGraphQl\Model\Config\SortAttributeReader</item> + <item name="productFilterDynamicAttributeReader" xsi:type="object">Magento\CatalogGraphQl\Model\Config\FilterAttributeReader</item> </argument> </arguments> </virtualType> diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index 2292004f3cf01..8fe3d19619fc3 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -48,6 +48,12 @@ <item name="radio" xsi:type="string">CustomizableRadioOption</item> <item name="checkbox" xsi:type="string">CustomizableCheckboxOption</item> </item> + <item name="sort_attributes" xsi:type="array"> + <item name="product_sort_attributes" xsi:type="string">ProductSortInput</item> + </item> + <item name="filter_attributes" xsi:type="array"> + <item name="product_filter_attributes" xsi:type="string">ProductFilterInput</item> + </item> </argument> </arguments> </type> From 10796a3ad68b74579deb8fe034cc3bdabc997301 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Wed, 7 Aug 2019 11:35:36 -0500 Subject: [PATCH 186/841] MC-18988: Investigate layer navigation code in lightweight resolver implementation --- .../Model/Resolver/LayerFilters.php | 19 +++++++++-- .../Model/Resolver/Products.php | 4 ++- .../Model/Resolver/Products/Query/Search.php | 32 +++++++++++++------ .../CatalogGraphQl/etc/schema.graphqls | 8 +++++ .../Query/Resolver/Argument/AstConverter.php | 13 +++++++- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php index 0ec7e12e42d55..d493c7ba8e66f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php @@ -10,6 +10,7 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +//use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilder; /** * Layered navigation filters resolver, used for GraphQL request processing. @@ -21,13 +22,24 @@ class LayerFilters implements ResolverInterface */ private $filtersDataProvider; +// /** +// * @var LayerBuilder +// */ +// private $layerBuilder; + /** * @param \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider + * @param \Magento\Framework\Registry $registry */ public function __construct( - \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider + \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider, + \Magento\Framework\Registry $registry + //LayerBuilder $layerBuilder + ) { $this->filtersDataProvider = $filtersDataProvider; + $this->registry = $registry; + //$this->layerBuilder = $layerBuilder; } /** @@ -43,7 +55,8 @@ public function resolve( if (!isset($value['layer_type'])) { return null; } - - return $this->filtersDataProvider->getData($value['layer_type']); + $aggregations = $this->registry->registry('aggregations'); + return []; + //return $this->layerBuilder->build($aggregations, 1); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index a75a9d2cf50a0..93ecf9c881548 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -89,7 +89,8 @@ public function resolve( $searchResult = $this->searchQuery->getResult($searchCriteria, $info); } else { $layerType = Resolver::CATALOG_LAYER_CATEGORY; - $searchResult = $this->filterQuery->getResult($searchCriteria, $info); + $searchCriteria->setRequestName('catalog_view_container'); + $searchResult = $this->searchQuery->getResult($searchCriteria, $info); } //possible division by 0 if ($searchCriteria->getPageSize()) { @@ -116,6 +117,7 @@ public function resolve( 'current_page' => $currentPage, 'total_pages' => $maxPages ], + //'filters' => $aggregations 'layer_type' => $layerType ]; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index bc40c664425ff..7a766269af602 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -13,6 +13,7 @@ use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory; use Magento\Search\Api\SearchInterface; +use Magento\Framework\Api\Search\SearchCriteriaInterfaceFactory; /** * Full text search for catalog using given search criteria. @@ -49,6 +50,11 @@ class Search */ private $pageSizeProvider; + /** + * @var SearchCriteriaInterfaceFactory + */ + private $searchCriteriaFactory; + /** * @param SearchInterface $search * @param FilterHelper $filterHelper @@ -56,6 +62,8 @@ class Search * @param SearchResultFactory $searchResultFactory * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Search\Model\Search\PageSizeProvider $pageSize + * @param SearchCriteriaInterfaceFactory $searchCriteriaFactory + * @param \Magento\Framework\Registry $registry */ public function __construct( SearchInterface $search, @@ -63,7 +71,9 @@ public function __construct( Filter $filterQuery, SearchResultFactory $searchResultFactory, \Magento\Framework\EntityManager\MetadataPool $metadataPool, - \Magento\Search\Model\Search\PageSizeProvider $pageSize + \Magento\Search\Model\Search\PageSizeProvider $pageSize, + SearchCriteriaInterfaceFactory $searchCriteriaFactory, + \Magento\Framework\Registry $registry ) { $this->search = $search; $this->filterHelper = $filterHelper; @@ -71,6 +81,8 @@ public function __construct( $this->searchResultFactory = $searchResultFactory; $this->metadataPool = $metadataPool; $this->pageSizeProvider = $pageSize; + $this->searchCriteriaFactory = $searchCriteriaFactory; + $this->registry = $registry; } /** @@ -89,11 +101,11 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $realPageSize = $searchCriteria->getPageSize(); $realCurrentPage = $searchCriteria->getCurrentPage(); // Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround - $pageSize = $this->pageSizeProvider->getMaxPageSize(); - $searchCriteria->setPageSize($pageSize); + //$pageSize = $this->pageSizeProvider->getMaxPageSize(); + $searchCriteria->setPageSize(10000); $searchCriteria->setCurrentPage(0); $itemsResults = $this->search->search($searchCriteria); - + $this->registry->register('aggregations', $itemsResults->getAggregations()); $ids = []; $searchIds = []; foreach ($itemsResults->getItems() as $item) { @@ -101,14 +113,14 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $searchIds[] = $item->getId(); } + $searchCriteriaIds = $this->searchCriteriaFactory->create(); $filter = $this->filterHelper->generate($idField, 'in', $searchIds); - $searchCriteria = $this->filterHelper->remove($searchCriteria, 'search_term'); - $searchCriteria = $this->filterHelper->add($searchCriteria, $filter); - $searchResult = $this->filterQuery->getResult($searchCriteria, $info, true); + $searchCriteriaIds = $this->filterHelper->add($searchCriteriaIds, $filter); + $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, true); - $searchCriteria->setPageSize($realPageSize); - $searchCriteria->setCurrentPage($realCurrentPage); - $paginatedProducts = $this->paginateList($searchResult, $searchCriteria); + $searchCriteriaIds->setPageSize($realPageSize); + $searchCriteriaIds->setCurrentPage($realCurrentPage); + $paginatedProducts = $this->paginateList($searchResult, $searchCriteriaIds); $products = []; if (!isset($searchCriteria->getSortOrders()[0])) { diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index ea56faf94408e..0b71b849d09a4 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -315,6 +315,14 @@ input ProductFilterInput @doc(description: "ProductFilterInput defines the filte country_of_manufacture: FilterTypeInput @doc(description: "The product's country of origin.") custom_layout: FilterTypeInput @doc(description: "The name of a custom layout.") gift_message_available: FilterTypeInput @doc(description: "Indicates whether a gift message is available.") + cat: FilterTypeInput @doc(description: "CATEGORY attribute name") + mysize: FilterTypeInput @doc(description: "size attribute name") + mycolor: FilterTypeInput @doc(description: "color attribute name") + ca_1_631447041: FilterTypeInput @doc(description: "CATEGORY attribute name") + attributeset2attribute1: FilterTypeInput @doc(description: "CATEGORY attribute name") + visibility: FilterTypeInput @doc(description: "CATEGORY attribute name") + price_dynamic_algorithm: FilterTypeInput @doc(description: "CATEGORY attribute name") + category_ids: FilterTypeInput @doc(description: "CATEGORY attribute name") or: ProductFilterInput @doc(description: "The keyword required to perform a logical OR comparison.") } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php index 0b03fc509c786..802d46eafcb97 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php @@ -54,10 +54,19 @@ public function __construct( * @param string $fieldName * @param array $arguments * @return array + * @throws \LogicException */ public function getClausesFromAst(string $fieldName, array $arguments) : array { $attributes = $this->fieldEntityAttributesPool->getEntityAttributesForEntityFromField($fieldName); + $attributes[] = 'cat'; + $attributes[] = 'mysize'; + $attributes[] = 'mycolor'; + $attributes[] = 'ca_1_631447041'; + $attributes[] = 'attributeset2attribute1'; + $attributes[] = 'price_dynamic_algorithm'; + $attributes[] = 'visibility'; + $attributes[] = 'category_ids'; $conditions = []; foreach ($arguments as $argumentName => $argument) { if (in_array($argumentName, $attributes)) { @@ -76,12 +85,14 @@ public function getClausesFromAst(string $fieldName, array $arguments) : array $value ); } - } else { + } elseif (is_array($argument)) { $conditions[] = $this->connectiveFactory->create( $this->getClausesFromAst($fieldName, $argument), $argumentName ); + } else { + throw new \LogicException('Attribute not found in the visible attributes list'); } } return $conditions; From a2156e1f623d99357c8b22f26fcc2fcc4bace3d7 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 8 Aug 2019 16:20:14 -0500 Subject: [PATCH 187/841] MC-18988: Investigate layer navigation code in lightweight resolver implementation --- .../DataProvider/AttributeQuery.php | 354 ++++++++++++++++++ .../Category/Query/CategoryAttributeQuery.php | 58 +++ .../DataProvider/CategoryAttributesMapper.php | 114 ++++++ .../AttributeOptionProvider.php | 107 ++++++ .../LayeredNavigation/Builder/Attribute.php | 179 +++++++++ .../LayeredNavigation/Builder/Category.php | 173 +++++++++ .../LayeredNavigation/Builder/Price.php | 107 ++++++ .../LayeredNavigation/LayerBuilder.php | 43 +++ .../LayerBuilderInterface.php | 40 ++ .../RootCategoryProvider.php | 55 +++ .../Model/Resolver/LayerFilters.php | 24 +- .../Magento/CatalogGraphQl/etc/graphql/di.xml | 10 + 12 files changed, 1254 insertions(+), 10 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/AttributeQuery.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilderInterface.php create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/RootCategoryProvider.php diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/AttributeQuery.php b/app/code/Magento/CatalogGraphQl/DataProvider/AttributeQuery.php new file mode 100644 index 0000000000000..b0f085932bb8e --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/AttributeQuery.php @@ -0,0 +1,354 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider; + +use Magento\Eav\Model\Config; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * Generic for build Select object to fetch eav attributes for provided entity type + */ +class AttributeQuery +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var string + */ + private $entityType; + + /** + * List of attributes that need to be added/removed to fetch + * + * @var array + */ + private $linkedAttributes; + + /** + * @var array + */ + private const SUPPORTED_BACKEND_TYPES = [ + 'int', + 'decimal', + 'text', + 'varchar', + 'datetime', + ]; + + /** + * @var int[] + */ + private $entityTypeIdMap; + + /** + * @var Config + */ + private $eavConfig; + + /** + * @param string $entityType + * @param ResourceConnection $resourceConnection + * @param MetadataPool $metadataPool + * @param Config $eavConfig + * @param array $linkedAttributes + */ + public function __construct( + string $entityType, + ResourceConnection $resourceConnection, + MetadataPool $metadataPool, + Config $eavConfig, + array $linkedAttributes = [] + ) { + $this->resourceConnection = $resourceConnection; + $this->metadataPool = $metadataPool; + $this->entityType = $entityType; + $this->linkedAttributes = $linkedAttributes; + $this->eavConfig = $eavConfig; + } + + /** + * Form and return query to get eav entity $attributes for given $entityIds. + * + * If eav entities were not found, then data is fetching from $entityTableName. + * + * @param array $entityIds + * @param array $attributes + * @param int $storeId + * @return Select + * @throws \Zend_Db_Select_Exception + * @throws \Exception + */ + public function getQuery(array $entityIds, array $attributes, int $storeId): Select + { + /** @var \Magento\Framework\EntityManager\EntityMetadataInterface $metadata */ + $metadata = $this->metadataPool->getMetadata($this->entityType); + $entityTableName = $metadata->getEntityTable(); + + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $connection */ + $connection = $this->resourceConnection->getConnection(); + $entityTableAttributes = \array_keys($connection->describeTable($entityTableName)); + + $attributeMetadataTable = $this->resourceConnection->getTableName('eav_attribute'); + $eavAttributes = $this->getEavAttributeCodes($attributes, $entityTableAttributes); + $entityTableAttributes = \array_intersect($attributes, $entityTableAttributes); + + $eavAttributesMetaData = $this->getAttributesMetaData($connection, $attributeMetadataTable, $eavAttributes); + + if ($eavAttributesMetaData) { + $select = $this->getEavAttributes( + $connection, + $metadata, + $entityTableAttributes, + $entityIds, + $eavAttributesMetaData, + $entityTableName, + $storeId + ); + } else { + $select = $this->getAttributesFromEntityTable( + $connection, + $entityTableAttributes, + $entityIds, + $entityTableName + ); + } + + return $select; + } + + /** + * Form and return query to get entity $entityTableAttributes for given $entityIds + * + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection + * @param array $entityTableAttributes + * @param array $entityIds + * @param string $entityTableName + * @return Select + */ + private function getAttributesFromEntityTable( + \Magento\Framework\DB\Adapter\AdapterInterface $connection, + array $entityTableAttributes, + array $entityIds, + string $entityTableName + ): Select { + $select = $connection->select() + ->from(['e' => $entityTableName], $entityTableAttributes) + ->where('e.entity_id IN (?)', $entityIds); + + return $select; + } + + /** + * Return ids of eav attributes by $eavAttributeCodes. + * + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection + * @param string $attributeMetadataTable + * @param array $eavAttributeCodes + * @return array + */ + private function getAttributesMetaData( + \Magento\Framework\DB\Adapter\AdapterInterface $connection, + string $attributeMetadataTable, + array $eavAttributeCodes + ): array { + $eavAttributeIdsSelect = $connection->select() + ->from(['a' => $attributeMetadataTable], ['attribute_id', 'backend_type', 'attribute_code']) + ->where('a.attribute_code IN (?)', $eavAttributeCodes) + ->where('a.entity_type_id = ?', $this->getEntityTypeId()); + + return $connection->fetchAssoc($eavAttributeIdsSelect); + } + + /** + * Form and return query to get eav entity $attributes for given $entityIds. + * + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection + * @param \Magento\Framework\EntityManager\EntityMetadataInterface $metadata + * @param array $entityTableAttributes + * @param array $entityIds + * @param array $eavAttributesMetaData + * @param string $entityTableName + * @param int $storeId + * @return Select + * @throws \Zend_Db_Select_Exception + */ + private function getEavAttributes( + \Magento\Framework\DB\Adapter\AdapterInterface $connection, + \Magento\Framework\EntityManager\EntityMetadataInterface $metadata, + array $entityTableAttributes, + array $entityIds, + array $eavAttributesMetaData, + string $entityTableName, + int $storeId + ): Select { + $selects = []; + $attributeValueExpression = $connection->getCheckSql( + $connection->getIfNullSql('store_eav.value_id', -1) . ' > 0', + 'store_eav.value', + 'eav.value' + ); + $linkField = $metadata->getLinkField(); + $attributesPerTable = $this->getAttributeCodeTables($entityTableName, $eavAttributesMetaData); + foreach ($attributesPerTable as $attributeTable => $eavAttributes) { + $attributeCodeExpression = $this->buildAttributeCodeExpression($eavAttributes); + + $selects[] = $connection->select() + ->from(['e' => $entityTableName], $entityTableAttributes) + ->joinLeft( + ['eav' => $this->resourceConnection->getTableName($attributeTable)], + \sprintf('e.%1$s = eav.%1$s', $linkField) . + $connection->quoteInto(' AND eav.attribute_id IN (?)', \array_keys($eavAttributesMetaData)) . + $connection->quoteInto(' AND eav.store_id = ?', \Magento\Store\Model\Store::DEFAULT_STORE_ID), + [] + ) + ->joinLeft( + ['store_eav' => $this->resourceConnection->getTableName($attributeTable)], + \sprintf( + 'e.%1$s = store_eav.%1$s AND store_eav.attribute_id = ' . + 'eav.attribute_id and store_eav.store_id = %2$d', + $linkField, + $storeId + ), + [] + ) + ->where('e.entity_id IN (?)', $entityIds) + ->columns( + [ + 'attribute_code' => $attributeCodeExpression, + 'value' => $attributeValueExpression + ] + ); + } + + return $connection->select()->union($selects, Select::SQL_UNION_ALL); + } + + /** + * Build expression for attribute code field. + * + * An example: + * + * ``` + * CASE + * WHEN eav.attribute_id = '73' THEN 'name' + * WHEN eav.attribute_id = '121' THEN 'url_key' + * END + * ``` + * + * @param array $eavAttributes + * @return \Zend_Db_Expr + */ + private function buildAttributeCodeExpression(array $eavAttributes): \Zend_Db_Expr + { + $dbConnection = $this->resourceConnection->getConnection(); + $expressionParts = ['CASE']; + + foreach ($eavAttributes as $attribute) { + $expressionParts[]= + $dbConnection->quoteInto('WHEN eav.attribute_id = ?', $attribute['attribute_id'], \Zend_Db::INT_TYPE) . + $dbConnection->quoteInto(' THEN ?', $attribute['attribute_code'], 'string'); + } + + $expressionParts[]= 'END'; + + return new \Zend_Db_Expr(implode(' ', $expressionParts)); + } + + /** + * Get list of attribute tables. + * + * Returns result in the following format: * + * ``` + * $attributeAttributeCodeTables = [ + * 'm2_catalog_product_entity_varchar' => + * '45' => [ + * 'attribute_id' => 45, + * 'backend_type' => 'varchar', + * 'name' => attribute_code, + * ] + * ] + * ]; + * ``` + * + * @param string $entityTable + * @param array $eavAttributesMetaData + * @return array + */ + private function getAttributeCodeTables($entityTable, $eavAttributesMetaData): array + { + $attributeAttributeCodeTables = []; + $metaTypes = \array_unique(\array_column($eavAttributesMetaData, 'backend_type')); + + foreach ($metaTypes as $type) { + if (\in_array($type, self::SUPPORTED_BACKEND_TYPES, true)) { + $tableName = \sprintf('%s_%s', $entityTable, $type); + $attributeAttributeCodeTables[$tableName] = array_filter( + $eavAttributesMetaData, + function ($attribute) use ($type) { + return $attribute['backend_type'] === $type; + } + ); + } + } + + return $attributeAttributeCodeTables; + } + + /** + * Get EAV attribute codes + * Remove attributes from entity table and attributes from exclude list + * Add linked attributes to output + * + * @param array $attributes + * @param array $entityTableAttributes + * @return array + */ + private function getEavAttributeCodes($attributes, $entityTableAttributes): array + { + $attributes = \array_diff($attributes, $entityTableAttributes); + $unusedAttributeList = []; + $newAttributes = []; + foreach ($this->linkedAttributes as $attribute => $linkedAttributes) { + if (null === $linkedAttributes) { + $unusedAttributeList[] = $attribute; + } elseif (\is_array($linkedAttributes) && \in_array($attribute, $attributes, true)) { + $newAttributes[] = $linkedAttributes; + } + } + $attributes = \array_diff($attributes, $unusedAttributeList); + + return \array_unique(\array_merge($attributes, ...$newAttributes)); + } + + /** + * Retrieve entity type id + * + * @return int + * @throws \Exception + */ + private function getEntityTypeId(): int + { + if (!isset($this->entityTypeIdMap[$this->entityType])) { + $this->entityTypeIdMap[$this->entityType] = (int)$this->eavConfig->getEntityType( + $this->metadataPool->getMetadata($this->entityType)->getEavEntityType() + )->getId(); + } + + return $this->entityTypeIdMap[$this->entityType]; + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php b/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php new file mode 100644 index 0000000000000..0b796c1457254 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Category\Query; + +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Framework\DB\Select; + +/** + * Provide category attributes for specified category ids and attributes + */ +class CategoryAttributeQuery +{ + /** + * @var \Magento\CatalogGraphQl\DataProvider\AttributeQueryFactory + */ + private $attributeQueryFactory; + + /** + * @var array + */ + private static $requiredAttributes = [ + 'entity_id', + ]; + + /** + * @param \Magento\CatalogGraphQl\DataProvider\AttributeQueryFactory $attributeQueryFactory + */ + public function __construct( + \Magento\CatalogGraphQl\DataProvider\AttributeQueryFactory $attributeQueryFactory + ) { + $this->attributeQueryFactory = $attributeQueryFactory; + } + + /** + * Form and return query to get eav attributes for given categories + * + * @param array $categoryIds + * @param array $categoryAttributes + * @param int $storeId + * @return Select + * @throws \Zend_Db_Select_Exception + */ + public function getQuery(array $categoryIds, array $categoryAttributes, int $storeId): Select + { + $categoryAttributes = \array_merge($categoryAttributes, self::$requiredAttributes); + + $attributeQuery = $this->attributeQueryFactory->create([ + 'entityType' => CategoryInterface::class + ]); + + return $attributeQuery->getQuery($categoryIds, $categoryAttributes, $storeId); + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php b/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php new file mode 100644 index 0000000000000..1f8aa38d5b939 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider; + +use Magento\Framework\GraphQl\ConfigInterface; +use Magento\Framework\GraphQl\Config\Element\Type; +use Magento\Framework\GraphQl\Config\Element\InterfaceType; + +/** + * Map for category attributes. + */ +class CategoryAttributesMapper +{ + /** + * @var ConfigInterface + */ + private $graphqlConfig; + + /** + * @param ConfigInterface $graphqlConfig + */ + public function __construct( + ConfigInterface $graphqlConfig + ) { + $this->graphqlConfig = $graphqlConfig; + } + + /** + * Returns attribute values for given attribute codes. + * + * @param array $fetchResult + * @return array + */ + public function getAttributesValues(array $fetchResult): array + { + $attributes = []; + + foreach ($fetchResult as $row) { + if (!isset($attributes[$row['entity_id']])) { + $attributes[$row['entity_id']] = $row; + //TODO: do we need to introduce field mapping? + $attributes[$row['entity_id']]['id'] = $row['entity_id']; + } + if (isset($row['attribute_code'])) { + $attributes[$row['entity_id']][$row['attribute_code']] = $row['value']; + } + } + + return $this->formatAttributes($attributes); + } + + /** + * Format attributes that should be converted to array type + * + * @param array $attributes + * @return array + */ + private function formatAttributes(array $attributes): array + { + $arrayTypeAttributes = $this->getFieldsOfArrayType(); + + return $arrayTypeAttributes + ? array_map(function ($data) use ($arrayTypeAttributes) { + foreach ($arrayTypeAttributes as $attributeCode) { + $data[$attributeCode] = $this->valueToArray($data[$attributeCode] ?? null); + } + return $data; + }, $attributes) + : $attributes; + } + + /** + * Cast string to array + * + * @param string|null $value + * @return array + */ + private function valueToArray($value): array + { + return $value ? \explode(',', $value) : []; + } + + /** + * Get fields that should be converted to array type + * + * @return array + */ + private function getFieldsOfArrayType(): array + { + $categoryTreeSchema = $this->graphqlConfig->getConfigElement('CategoryTree'); + if (!$categoryTreeSchema instanceof Type) { + throw new \LogicException('CategoryTree type not defined in schema.'); + } + + $fields = []; + foreach ($categoryTreeSchema->getInterfaces() as $interface) { + /** @var InterfaceType $configElement */ + $configElement = $this->graphqlConfig->getConfigElement($interface['interface']); + + foreach ($configElement->getFields() as $field) { + if ($field->isList()) { + $fields[] = $field->getName(); + } + } + } + + return $fields; + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php new file mode 100644 index 0000000000000..7781473128754 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation; + +use Magento\Framework\App\ResourceConnection; + +/** + * Fetch product attribute option data including attribute info + * Return data in format: + * [ + * attribute_code => [ + * attribute_code => code, + * attribute_label => attribute label, + * option_label => option label, + * options => [option_id => 'option label', ...], + * ] + * ... + * ] + */ +class AttributeOptionProvider +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param ResourceConnection $resourceConnection + */ + public function __construct(ResourceConnection $resourceConnection) + { + $this->resourceConnection = $resourceConnection; + } + + /** + * Get option data. Return list of attributes with option data + * + * @param array $optionIds + * @return array + * @throws \Zend_Db_Statement_Exception + */ + public function getOptions(array $optionIds): array + { + if (!$optionIds) { + return []; + } + + $connection = $this->resourceConnection->getConnection(); + $select = $connection->select() + ->from( + ['a' => $this->resourceConnection->getTableName('eav_attribute')], + [ + 'attribute_id' => 'a.attribute_id', + 'attribute_code' => 'a.attribute_code', + 'attribute_label' => 'a.frontend_label', + ] + ) + ->joinInner( + ['options' => $this->resourceConnection->getTableName('eav_attribute_option')], + 'a.attribute_id = options.attribute_id', + [] + ) + ->joinInner( + ['option_value' => $this->resourceConnection->getTableName('eav_attribute_option_value')], + 'options.option_id = option_value.option_id', + [ + 'option_label' => 'option_value.value', + 'option_id' => 'option_value.option_id', + ] + ) + ->where('option_value.option_id IN (?)', $optionIds); + + return $this->formatResult($select); + } + + /** + * Format result + * + * @param \Magento\Framework\DB\Select $select + * @return array + * @throws \Zend_Db_Statement_Exception + */ + private function formatResult(\Magento\Framework\DB\Select $select): array + { + $statement = $this->resourceConnection->getConnection()->query($select); + + $result = []; + while ($option = $statement->fetch()) { + if (!isset($result[$option['attribute_code']])) { + $result[$option['attribute_code']] = [ + 'attribute_id' => $option['attribute_id'], + 'attribute_code' => $option['attribute_code'], + 'attribute_label' => $option['attribute_label'], + 'options' => [], + ]; + } + $result[$option['attribute_code']]['options'][$option['option_id']] = $option['option_label']; + } + + return $result; + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php new file mode 100644 index 0000000000000..82d167e323fc8 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php @@ -0,0 +1,179 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder; + +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\AttributeOptionProvider; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilderInterface; +use Magento\Framework\Api\Search\AggregationInterface; +use Magento\Framework\Api\Search\AggregationValueInterface; +use Magento\Framework\Api\Search\BucketInterface; + +/** + * @inheritdoc + */ +class Attribute implements LayerBuilderInterface +{ + /** + * @var string + * @see \Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Category::CATEGORY_BUCKET + */ + private const PRICE_BUCKET = 'price_bucket'; + + /** + * @var string + * @see \Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Price::PRICE_BUCKET + */ + private const CATEGORY_BUCKET = 'category_bucket'; + + /** + * @var AttributeOptionProvider + */ + private $attributeOptionProvider; + + /** + * @var array + */ + private $bucketNameFilter = [ + self::PRICE_BUCKET, + self::CATEGORY_BUCKET + ]; + + /** + * @param AttributeOptionProvider $attributeOptionProvider + * @param array $bucketNameFilter List with buckets name to be removed from filter + */ + public function __construct( + AttributeOptionProvider $attributeOptionProvider, + $bucketNameFilter = [] + ) { + $this->attributeOptionProvider = $attributeOptionProvider; + $this->bucketNameFilter = \array_merge($this->bucketNameFilter, $bucketNameFilter); + } + + /** + * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @throws \Zend_Db_Statement_Exception + */ + public function build(AggregationInterface $aggregation, ?int $storeId): array + { + $attributeOptions = $this->getAttributeOptions($aggregation); + + // build layer per attribute + $result = []; + foreach ($this->getAttributeBuckets($aggregation) as $bucket) { + $bucketName = $bucket->getName(); + $attributeCode = \preg_replace('~_bucket$~', '', $bucketName); + $attribute = $attributeOptions[$attributeCode] ?? []; + + $result[$bucketName] = $this->buildLayer( + $attribute['attribute_label'] ?? $bucketName, + \count($bucket->getValues()), + $attribute['attribute_code'] ?? $bucketName + ); + + foreach ($bucket->getValues() as $value) { + $metrics = $value->getMetrics(); + $result[$bucketName]['filter_items'][] = $this->buildItem( + $attribute['options'][$metrics['value']] ?? $metrics['value'], + $metrics['value'], + $metrics['count'] + ); + } + } + + return $result; + } + + /** + * Get attribute buckets excluding specified bucket names + * + * @param AggregationInterface $aggregation + * @return \Generator|BucketInterface[] + */ + private function getAttributeBuckets(AggregationInterface $aggregation) + { + foreach ($aggregation->getBuckets() as $bucket) { + if (\in_array($bucket->getName(), $this->bucketNameFilter, true)) { + continue; + } + if ($this->isBucketEmpty($bucket)) { + continue; + } + yield $bucket; + } + } + + /** + * Format layer data + * + * @param string $layerName + * @param string $itemsCount + * @param string $requestName + * @return array + */ + private function buildLayer($layerName, $itemsCount, $requestName): array + { + return [ + 'name' => $layerName, + 'filter_items_count' => $itemsCount, + 'request_var' => $requestName + ]; + } + + /** + * Format layer item data + * + * @param string $label + * @param string|int $value + * @param string|int $count + * @return array + */ + private function buildItem($label, $value, $count): array + { + return [ + 'label' => $label, + 'value_string' => $value, + 'items_count' => $count, + ]; + } + + /** + * Check that bucket contains data + * + * @param BucketInterface|null $bucket + * @return bool + */ + private function isBucketEmpty(?BucketInterface $bucket): bool + { + return null === $bucket || !$bucket->getValues(); + } + + /** + * Get list of attributes with options + * + * @param AggregationInterface $aggregation + * @return array + * @throws \Zend_Db_Statement_Exception + */ + private function getAttributeOptions(AggregationInterface $aggregation): array + { + $attributeOptionIds = []; + foreach ($this->getAttributeBuckets($aggregation) as $bucket) { + $attributeOptionIds[] = \array_map(function (AggregationValueInterface $value) { + return $value->getValue(); + }, $bucket->getValues()); + } + + if (!$attributeOptionIds) { + return []; + } + + return $this->attributeOptionProvider->getOptions(\array_merge(...$attributeOptionIds)); + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php new file mode 100644 index 0000000000000..c726f66e8c926 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php @@ -0,0 +1,173 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder; + +use Magento\CatalogGraphQl\DataProvider\CategoryAttributesMapper; +use Magento\CatalogGraphQl\DataProvider\Category\Query\CategoryAttributeQuery; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilderInterface; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\RootCategoryProvider; +use Magento\Framework\Api\Search\AggregationInterface; +use Magento\Framework\Api\Search\AggregationValueInterface; +use Magento\Framework\Api\Search\BucketInterface; +use Magento\Framework\App\ResourceConnection; + +/** + * @inheritdoc + */ +class Category implements LayerBuilderInterface +{ + /** + * @var string + */ + private const CATEGORY_BUCKET = 'category_bucket'; + + /** + * @var array + */ + private static $bucketMap = [ + self::CATEGORY_BUCKET => [ + 'request_name' => 'category_id', + 'label' => 'Category' + ], + ]; + + /** + * @var CategoryAttributeQuery + */ + private $categoryAttributeQuery; + + /** + * @var CategoryAttributesMapper + */ + private $attributesMapper; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var RootCategoryProvider + */ + private $rootCategoryProvider; + + /** + * @param CategoryAttributeQuery $categoryAttributeQuery + * @param CategoryAttributesMapper $attributesMapper + * @param RootCategoryProvider $rootCategoryProvider + * @param ResourceConnection $resourceConnection + */ + public function __construct( + CategoryAttributeQuery $categoryAttributeQuery, + CategoryAttributesMapper $attributesMapper, + RootCategoryProvider $rootCategoryProvider, + ResourceConnection $resourceConnection + ) { + $this->categoryAttributeQuery = $categoryAttributeQuery; + $this->attributesMapper = $attributesMapper; + $this->resourceConnection = $resourceConnection; + $this->rootCategoryProvider = $rootCategoryProvider; + } + + /** + * @inheritdoc + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Zend_Db_Select_Exception + */ + public function build(AggregationInterface $aggregation, ?int $storeId): array + { + $bucket = $aggregation->getBucket(self::CATEGORY_BUCKET); + if ($this->isBucketEmpty($bucket)) { + return []; + } + + $categoryIds = \array_map(function (AggregationValueInterface $value) { + return (int)$value->getValue(); + }, $bucket->getValues()); + + $categoryIds = \array_diff($categoryIds, [$this->rootCategoryProvider->getRootCategory($storeId)]); + $categoryLabels = \array_column( + $this->attributesMapper->getAttributesValues( + $this->resourceConnection->getConnection()->fetchAll( + $this->categoryAttributeQuery->getQuery($categoryIds, ['name'], $storeId) + ) + ), + 'name', + 'entity_id' + ); + + if (!$categoryLabels) { + return []; + } + + $result = $this->buildLayer( + self::$bucketMap[self::CATEGORY_BUCKET]['label'], + \count($categoryIds), + self::$bucketMap[self::CATEGORY_BUCKET]['request_name'] + ); + + foreach ($bucket->getValues() as $value) { + $categoryId = $value->getValue(); + if (!\in_array($categoryId, $categoryIds, true)) { + continue ; + } + $result['filter_items'][] = $this->buildItem( + $categoryLabels[$categoryId] ?? $categoryId, + $categoryId, + $value->getMetrics()['count'] + ); + } + + return [$result]; + } + + /** + * Format layer data + * + * @param string $layerName + * @param string $itemsCount + * @param string $requestName + * @return array + */ + private function buildLayer($layerName, $itemsCount, $requestName): array + { + return [ + 'name' => $layerName, + 'filter_items_count' => $itemsCount, + 'request_var' => $requestName + ]; + } + + /** + * Format layer item data + * + * @param string $label + * @param string|int $value + * @param string|int $count + * @return array + */ + private function buildItem($label, $value, $count): array + { + return [ + 'label' => $label, + 'value_string' => $value, + 'items_count' => $count, + ]; + } + + /** + * Check that bucket contains data + * + * @param BucketInterface|null $bucket + * @return bool + */ + private function isBucketEmpty(?BucketInterface $bucket): bool + { + return null === $bucket || !$bucket->getValues(); + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php new file mode 100644 index 0000000000000..77f44afb4f679 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php @@ -0,0 +1,107 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder; + +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilderInterface; +use Magento\Framework\Api\Search\AggregationInterface; +use Magento\Framework\Api\Search\BucketInterface; + +/** + * @inheritdoc + */ +class Price implements LayerBuilderInterface +{ + /** + * @var string + */ + private const PRICE_BUCKET = 'price_bucket'; + + /** + * @var array + */ + private static $bucketMap = [ + self::PRICE_BUCKET => [ + 'request_name' => 'price', + 'label' => 'Price' + ], + ]; + + /** + * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function build(AggregationInterface $aggregation, ?int $storeId): array + { + $bucket = $aggregation->getBucket(self::PRICE_BUCKET); + if ($this->isBucketEmpty($bucket)) { + return []; + } + + $result = $this->buildLayer( + self::$bucketMap[self::PRICE_BUCKET]['label'], + \count($bucket->getValues()), + self::$bucketMap[self::PRICE_BUCKET]['request_name'] + ); + + foreach ($bucket->getValues() as $value) { + $metrics = $value->getMetrics(); + $result['filter_items'][] = $this->buildItem( + \str_replace('_', '-', $metrics['value']), + $metrics['value'], + $metrics['count'] + ); + } + + return [$result]; + } + + /** + * Format layer data + * + * @param string $layerName + * @param string $itemsCount + * @param string $requestName + * @return array + */ + private function buildLayer($layerName, $itemsCount, $requestName): array + { + return [ + 'name' => $layerName, + 'filter_items_count' => $itemsCount, + 'request_var' => $requestName + ]; + } + + /** + * Format layer item data + * + * @param string $label + * @param string|int $value + * @param string|int $count + * @return array + */ + private function buildItem($label, $value, $count): array + { + return [ + 'label' => $label, + 'value_string' => $value, + 'items_count' => $count, + ]; + } + + /** + * Check that bucket contains data + * + * @param BucketInterface|null $bucket + * @return bool + */ + private function isBucketEmpty(?BucketInterface $bucket): bool + { + return null === $bucket || !$bucket->getValues(); + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php new file mode 100644 index 0000000000000..ff661236be62f --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilder.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation; + +use Magento\Framework\Api\Search\AggregationInterface; + +/** + * @inheritdoc + */ +class LayerBuilder implements LayerBuilderInterface +{ + /** + * @var LayerBuilderInterface[] + */ + private $builders; + + /** + * @param LayerBuilderInterface[] $builders + */ + public function __construct(array $builders) + { + $this->builders = $builders; + } + + /** + * @inheritdoc + */ + public function build(AggregationInterface $aggregation, ?int $storeId): array + { + $layers = []; + foreach ($this->builders as $builder) { + $layers[] = $builder->build($aggregation, $storeId); + } + $layers = \array_merge(...$layers); + + return \array_filter($layers); + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilderInterface.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilderInterface.php new file mode 100644 index 0000000000000..bd55bc6938b39 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/LayerBuilderInterface.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation; + +use Magento\Framework\Api\Search\AggregationInterface; + +/** + * Build layer data from AggregationInterface + * Return data in the following format: + * + * [ + * [ + * 'name' => 'layer name', + * 'filter_items_count' => 'filter items count', + * 'request_var' => 'filter name in request', + * 'filter_items' => [ + * 'label' => 'item name', + * 'value_string' => 'item value, e.g. category ID', + * 'items_count' => 'product count', + * ], + * ], + * ... + * ]; + */ +interface LayerBuilderInterface +{ + /** + * Build layer data + * + * @param AggregationInterface $aggregation + * @param int|null $storeId + * @return array [[{layer data}], ...] + */ + public function build(AggregationInterface $aggregation, ?int $storeId): array; +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/RootCategoryProvider.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/RootCategoryProvider.php new file mode 100644 index 0000000000000..4b8a4a31b3c35 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/RootCategoryProvider.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation; + +use Magento\Framework\App\ResourceConnection; + +/** + * Fetch root category id for specified store id + */ +class RootCategoryProvider +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param ResourceConnection $resourceConnection + */ + public function __construct( + ResourceConnection $resourceConnection + ) { + $this->resourceConnection = $resourceConnection; + } + + /** + * Get root category for specified store id + * + * @param int $storeId + * @return int + */ + public function getRootCategory(int $storeId): int + { + $connection = $this->resourceConnection->getConnection(); + + $select = $connection->select() + ->from( + ['store' => $this->resourceConnection->getTableName('store')], + [] + ) + ->join( + ['store_group' => $this->resourceConnection->getTableName('store_group')], + 'store.group_id = store_group.group_id', + ['root_category_id' => 'store_group.root_category_id'] + ) + ->where('store.store_id = ?', $storeId); + + return (int)$connection->fetchOne($select); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php index d493c7ba8e66f..9aa632b4fafbb 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php @@ -10,7 +10,8 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -//use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilder; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilder; +use Magento\Store\Api\Data\StoreInterface; /** * Layered navigation filters resolver, used for GraphQL request processing. @@ -22,24 +23,25 @@ class LayerFilters implements ResolverInterface */ private $filtersDataProvider; -// /** -// * @var LayerBuilder -// */ -// private $layerBuilder; + /** + * @var LayerBuilder + */ + private $layerBuilder; /** * @param \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider * @param \Magento\Framework\Registry $registry + * @param LayerBuilder $layerBuilder */ public function __construct( \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider, - \Magento\Framework\Registry $registry - //LayerBuilder $layerBuilder + \Magento\Framework\Registry $registry, + LayerBuilder $layerBuilder ) { $this->filtersDataProvider = $filtersDataProvider; $this->registry = $registry; - //$this->layerBuilder = $layerBuilder; + $this->layerBuilder = $layerBuilder; } /** @@ -56,7 +58,9 @@ public function resolve( return null; } $aggregations = $this->registry->registry('aggregations'); - return []; - //return $this->layerBuilder->build($aggregations, 1); + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + $storeId = (int)$store->getId(); + return $this->layerBuilder->build($aggregations, $storeId); } } diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index 8fe3d19619fc3..3ada365ec5065 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -101,4 +101,14 @@ </argument> </arguments> </type> + + <type name="Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilder"> + <arguments> + <argument name="builders" xsi:type="array"> + <item name="price_bucket" xsi:type="object">Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Price</item> + <item name="category_bucket" xsi:type="object">Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Category</item> + <item name="attribute_bucket" xsi:type="object">Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Attribute</item> + </argument> + </arguments> + </type> </config> From 2837f1e985f1168e3630dc25c90c36cf1f132dec Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 9 Aug 2019 11:50:57 -0500 Subject: [PATCH 188/841] MC-18988: Investigate layer navigation code in lightweight resolver implementation --- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 0b71b849d09a4..1ce46cf3cbef9 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -315,11 +315,6 @@ input ProductFilterInput @doc(description: "ProductFilterInput defines the filte country_of_manufacture: FilterTypeInput @doc(description: "The product's country of origin.") custom_layout: FilterTypeInput @doc(description: "The name of a custom layout.") gift_message_available: FilterTypeInput @doc(description: "Indicates whether a gift message is available.") - cat: FilterTypeInput @doc(description: "CATEGORY attribute name") - mysize: FilterTypeInput @doc(description: "size attribute name") - mycolor: FilterTypeInput @doc(description: "color attribute name") - ca_1_631447041: FilterTypeInput @doc(description: "CATEGORY attribute name") - attributeset2attribute1: FilterTypeInput @doc(description: "CATEGORY attribute name") visibility: FilterTypeInput @doc(description: "CATEGORY attribute name") price_dynamic_algorithm: FilterTypeInput @doc(description: "CATEGORY attribute name") category_ids: FilterTypeInput @doc(description: "CATEGORY attribute name") From c3273036f6566abb0adc52be1996b26fe0218078 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sat, 10 Aug 2019 01:19:42 +0000 Subject: [PATCH 189/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProductPrice.php | 25 +++++++++++++------ .../Pricing/Price/CatalogRulePrice.php | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php index 6a87be3c50a64..aa29bcc14ba58 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php @@ -36,25 +36,33 @@ class ReindexRuleProductPrice */ private $pricesPersistor; + /** + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + private $localeDate; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param RuleProductsSelectBuilder $ruleProductsSelectBuilder * @param ProductPriceCalculator $productPriceCalculator * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime * @param \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder $ruleProductsSelectBuilder, \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator $productPriceCalculator, \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, - \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor + \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate ) { $this->storeManager = $storeManager; $this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder; $this->productPriceCalculator = $productPriceCalculator; $this->dateTime = $dateTime; $this->pricesPersistor = $pricesPersistor; + $this->localeDate = $localeDate; } /** @@ -71,12 +79,9 @@ public function execute( \Magento\Catalog\Model\Product $product = null, $useAdditionalTable = false ) { - $fromDate = mktime(0, 0, 0, date('m'), date('d') - 1); - $toDate = mktime(0, 0, 0, date('m'), date('d') + 1); - /** * Update products rules prices per each website separately - * because of max join limit in mysql + * because for each website date in website's timezone should be used */ foreach ($this->storeManager->getWebsites() as $website) { $productsStmt = $this->ruleProductsSelectBuilder->build($website->getId(), $product, $useAdditionalTable); @@ -84,6 +89,11 @@ public function execute( $stopFlags = []; $prevKey = null; + $storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId()); + $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId()); + $previousDate = (clone $currentDate)->modify('-1 day'); + $nextDate = (clone $currentDate)->modify('+1 day'); + while ($ruleData = $productsStmt->fetch()) { $ruleProductId = $ruleData['product_id']; $productKey = $ruleProductId . @@ -105,7 +115,8 @@ public function execute( /** * Build prices for each day */ - for ($time = $fromDate; $time <= $toDate; $time += IndexBuilder::SECONDS_IN_DAY) { + foreach ([$previousDate, $currentDate, $nextDate] as $date) { + $time = $date->getTimestamp(); if (($ruleData['from_time'] == 0 || $time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 || $time <= $ruleData['to_time']) @@ -118,7 +129,7 @@ public function execute( if (!isset($dayPrices[$priceKey])) { $dayPrices[$priceKey] = [ - 'rule_date' => $time, + 'rule_date' => $date, 'website_id' => $ruleData['website_id'], 'customer_group_id' => $ruleData['customer_group_id'], 'product_id' => $ruleProductId, diff --git a/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php b/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php index 8bce5456ffa72..7cbbc547571ab 100644 --- a/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Pricing/Price/CatalogRulePrice.php @@ -88,7 +88,7 @@ public function getValue() $this->value = (float)$this->product->getData(self::PRICE_CODE) ?: false; } else { $this->value = $this->ruleResource->getRulePrice( - $this->dateTime->date(null, null, false), + $this->dateTime->scopeDate($this->storeManager->getStore()->getId()), $this->storeManager->getStore()->getWebsiteId(), $this->customerSession->getCustomerGroupId(), $this->product->getId() From fafdc212f4eff1ae3144f336fbf5ad301d3665bd Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sat, 10 Aug 2019 15:42:43 +0000 Subject: [PATCH 190/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Catalog/Model/Product/Type/Price.php | 5 ++- .../Model/Indexer/ReindexRuleProductPrice.php | 34 ++++--------------- .../Product/CollectionProcessor.php | 2 +- ...ProductSelectBuilderByCatalogRulePrice.php | 3 +- ...CatalogProductCollectionPricesObserver.php | 2 +- .../ProcessAdminFinalPriceObserver.php | 3 +- .../ProcessFrontFinalPriceObserver.php | 2 +- 7 files changed, 17 insertions(+), 34 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index dc73baef3f768..23a058b5a57c9 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -596,7 +596,10 @@ public function calculatePrice( ) { \Magento\Framework\Profiler::start('__PRODUCT_CALCULATE_PRICE__'); if ($wId instanceof Store) { + $sId = $wId->getId(); $wId = $wId->getWebsiteId(); + } else { + $sId = $this->_storeManager->getWebsite($wId)->getDefaultGroup()->getDefaultStoreId(); } $finalPrice = $basePrice; @@ -610,7 +613,7 @@ public function calculatePrice( ); if ($rulePrice === false) { - $date = $this->_localeDate->date(null, null, false); + $date = $this->_localeDate->scopeDate($sId); $rulePrice = $this->_ruleFactory->create()->getRulePrice($date, $wId, $gId, $productId); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php index aa29bcc14ba58..ad905229de917 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php @@ -27,42 +27,34 @@ class ReindexRuleProductPrice private $productPriceCalculator; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface */ - private $dateTime; + private $localeDate; /** * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor */ private $pricesPersistor; - /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface - */ - private $localeDate; - /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param RuleProductsSelectBuilder $ruleProductsSelectBuilder * @param ProductPriceCalculator $productPriceCalculator - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder $ruleProductsSelectBuilder, \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator $productPriceCalculator, - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, - \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, + \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor ) { $this->storeManager = $storeManager; $this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder; $this->productPriceCalculator = $productPriceCalculator; - $this->dateTime = $dateTime; - $this->pricesPersistor = $pricesPersistor; $this->localeDate = $localeDate; + $this->pricesPersistor = $pricesPersistor; } /** @@ -110,8 +102,6 @@ public function execute( } } - $ruleData['from_time'] = $this->roundTime($ruleData['from_time']); - $ruleData['to_time'] = $this->roundTime($ruleData['to_time']); /** * Build prices for each day */ @@ -164,16 +154,4 @@ public function execute( } return true; } - - /** - * @param int $timeStamp - * @return int - */ - private function roundTime($timeStamp) - { - if (is_numeric($timeStamp) && $timeStamp != 0) { - $timeStamp = $this->dateTime->timestamp($this->dateTime->date('Y-m-d 00:00:00', $timeStamp)); - } - return $timeStamp; - } } diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php index 0dee9eda5b6e8..1fd6f0cbc986f 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/CollectionProcessor.php @@ -90,7 +90,7 @@ public function addPriceData(ProductCollection $productCollection, $joinColumn = ), $connection->quoteInto( 'catalog_rule.rule_date = ?', - $this->dateTime->formatDate($this->localeDate->date(null, null, false), false) + $this->dateTime->formatDate($this->localeDate->scopeDate($store->getId()), false) ), ] ), diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php index 02d2631058a1a..48c463fc18b80 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php @@ -88,7 +88,8 @@ public function __construct( */ public function build($productId) { - $currentDate = $this->dateTime->formatDate($this->localeDate->date(null, null, false), false); + $timestamp = $this->localeDate->scopeTimeStamp($this->storeManager->getStore()); + $currentDate = $this->dateTime->formatDate($timestamp, false); $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); diff --git a/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php b/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php index a635c5611eff6..bf0c85e671dd7 100644 --- a/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php +++ b/app/code/Magento/CatalogRule/Observer/PrepareCatalogProductCollectionPricesObserver.php @@ -105,7 +105,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) if ($observer->getEvent()->hasDate()) { $date = new \DateTime($observer->getEvent()->getDate()); } else { - $date = $this->localeDate->date(null, null, false); + $date = (new \DateTime())->setTimestamp($this->localeDate->scopeTimeStamp($store)); } $productIds = []; diff --git a/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php b/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php index 2fd23ae391474..89ed519cfb8c8 100644 --- a/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php +++ b/app/code/Magento/CatalogRule/Observer/ProcessAdminFinalPriceObserver.php @@ -65,7 +65,8 @@ public function __construct( public function execute(\Magento\Framework\Event\Observer $observer) { $product = $observer->getEvent()->getProduct(); - $date = $this->localeDate->date(null, null, false); + $storeId = $product->getStoreId(); + $date = $this->localeDate->scopeDate($storeId); $key = false; $ruleData = $this->coreRegistry->registry('rule_data'); diff --git a/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php b/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php index b27768ae091ed..075fe9e51f7dc 100644 --- a/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php +++ b/app/code/Magento/CatalogRule/Observer/ProcessFrontFinalPriceObserver.php @@ -80,7 +80,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) if ($observer->hasDate()) { $date = new \DateTime($observer->getEvent()->getDate()); } else { - $date = $this->localeDate->date(null, null, false); + $date = $this->localeDate->scopeDate($storeId); } if ($observer->hasWebsiteId()) { From 8ce55468c5f8b279a3b9daf6a602106f00597c54 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sat, 10 Aug 2019 20:44:33 +0000 Subject: [PATCH 191/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProductPrice.php | 41 +++--- .../Indexer/ReindexRuleProductPriceTest.php | 131 +++++++++--------- .../Pricing/Price/CatalogRulePriceTest.php | 8 +- 3 files changed, 94 insertions(+), 86 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php index ad905229de917..fb01ce82fb040 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php @@ -6,49 +6,53 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\Catalog\Model\Product; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\StoreManagerInterface; + /** * Reindex product prices according rule settings. */ class ReindexRuleProductPrice { /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ private $storeManager; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder + * @var RuleProductsSelectBuilder */ private $ruleProductsSelectBuilder; /** - * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator + * @var ProductPriceCalculator */ private $productPriceCalculator; /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var TimezoneInterface */ private $localeDate; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor + * @var RuleProductPricesPersistor */ private $pricesPersistor; /** - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param StoreManagerInterface $storeManager * @param RuleProductsSelectBuilder $ruleProductsSelectBuilder * @param ProductPriceCalculator $productPriceCalculator - * @param @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @param \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor + * @param TimezoneInterface $localeDate + * @param RuleProductPricesPersistor $pricesPersistor */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder $ruleProductsSelectBuilder, - \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator $productPriceCalculator, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor + StoreManagerInterface $storeManager, + RuleProductsSelectBuilder $ruleProductsSelectBuilder, + ProductPriceCalculator $productPriceCalculator, + TimezoneInterface $localeDate, + RuleProductPricesPersistor $pricesPersistor ) { $this->storeManager = $storeManager; $this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder; @@ -61,16 +65,13 @@ public function __construct( * Reindex product prices. * * @param int $batchCount - * @param \Magento\Catalog\Model\Product|null $product + * @param Product|null $product * @param bool $useAdditionalTable * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function execute( - $batchCount, - \Magento\Catalog\Model\Product $product = null, - $useAdditionalTable = false - ) { + public function execute($batchCount, Product $product = null, $useAdditionalTable = false) + { /** * Update products rules prices per each website separately * because for each website date in website's timezone should be used @@ -82,7 +83,7 @@ public function execute( $prevKey = null; $storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId()); - $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId()); + $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true); $previousDate = (clone $currentDate)->modify('-1 day'); $nextDate = (clone $currentDate)->modify('+1 day'); diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php index 6d7f0673ed281..5f63283df6760 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php @@ -6,65 +6,62 @@ namespace Magento\CatalogRule\Test\Unit\Model\Indexer; -use Magento\CatalogRule\Model\Indexer\IndexBuilder; +use Magento\Catalog\Model\Product; +use Magento\CatalogRule\Model\Indexer\ProductPriceCalculator; +use Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice; +use Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor; +use Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Api\Data\GroupInterface; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Model\StoreManagerInterface; +use PHPUnit\Framework\MockObject\MockObject; class ReindexRuleProductPriceTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice + * @var ReindexRuleProductPrice */ private $model; /** - * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|MockObject */ private $storeManagerMock; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var RuleProductsSelectBuilder|MockObject */ private $ruleProductsSelectBuilderMock; /** - * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator|\PHPUnit_Framework_MockObject_MockObject + * @var ProductPriceCalculator|MockObject */ private $productPriceCalculatorMock; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime|\PHPUnit_Framework_MockObject_MockObject + * @var TimezoneInterface|MockObject */ - private $dateTimeMock; + private $localeDate; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor|\PHPUnit_Framework_MockObject_MockObject + * @var RuleProductPricesPersistor|MockObject */ private $pricesPersistorMock; protected function setUp() { - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->ruleProductsSelectBuilderMock = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder::class) - ->disableOriginalConstructor() - ->getMock(); - $this->productPriceCalculatorMock = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\ProductPriceCalculator::class) - ->disableOriginalConstructor() - ->getMock(); - $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\DateTime::class) - ->disableOriginalConstructor() - ->getMock(); - $this->pricesPersistorMock = - $this->getMockBuilder(\Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor::class) - ->disableOriginalConstructor() - ->getMock(); - $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice( + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->ruleProductsSelectBuilderMock = $this->createMock(RuleProductsSelectBuilder::class); + $this->productPriceCalculatorMock = $this->createMock(ProductPriceCalculator::class); + $this->localeDate = $this->createMock(TimezoneInterface::class); + $this->pricesPersistorMock = $this->createMock(RuleProductPricesPersistor::class); + + $this->model = new ReindexRuleProductPrice( $this->storeManagerMock, $this->ruleProductsSelectBuilderMock, $this->productPriceCalculatorMock, - $this->dateTimeMock, + $this->localeDate, $this->pricesPersistorMock ); } @@ -72,19 +69,32 @@ protected function setUp() public function testExecute() { $websiteId = 234; - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $websiteMock = $this->getMockBuilder(\Magento\Store\Api\Data\WebsiteInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $websiteMock->expects($this->once())->method('getId')->willReturn($websiteId); - $this->storeManagerMock->expects($this->once())->method('getWebsites')->willReturn([$websiteMock]); - - $statementMock = $this->getMockBuilder(\Zend_Db_Statement_Interface::class) - ->disableOriginalConstructor() - ->getMock(); + $defaultGroupId = 11; + $defaultStoreId = 22; + + $websiteMock = $this->createMock(WebsiteInterface::class); + $websiteMock->expects($this->once()) + ->method('getId') + ->willReturn($websiteId); + $websiteMock->expects($this->once()) + ->method('getDefaultGroupId') + ->willReturn($defaultGroupId); + $this->storeManagerMock->expects($this->once()) + ->method('getWebsites') + ->willReturn([$websiteMock]); + $groupMock = $this->createMock(GroupInterface::class); + $groupMock->method('getId') + ->willReturn($defaultStoreId); + $groupMock->expects($this->once()) + ->method('getDefaultStoreId') + ->willReturn($defaultStoreId); + $this->storeManagerMock->expects($this->once()) + ->method('getGroup') + ->with($defaultGroupId) + ->willReturn($groupMock); + + $productMock = $this->createMock(Product::class); + $statementMock = $this->createMock(\Zend_Db_Statement_Interface::class); $this->ruleProductsSelectBuilderMock->expects($this->once()) ->method('build') ->with($websiteId, $productMock, true) @@ -99,29 +109,22 @@ public function testExecute() 'action_stop' => true ]; - $this->dateTimeMock->expects($this->at(0)) - ->method('date') - ->with('Y-m-d 00:00:00', $ruleData['from_time']) - ->willReturn($ruleData['from_time']); - $this->dateTimeMock->expects($this->at(1)) - ->method('timestamp') - ->with($ruleData['from_time']) - ->willReturn($ruleData['from_time']); - - $this->dateTimeMock->expects($this->at(2)) - ->method('date') - ->with('Y-m-d 00:00:00', $ruleData['to_time']) - ->willReturn($ruleData['to_time']); - $this->dateTimeMock->expects($this->at(3)) - ->method('timestamp') - ->with($ruleData['to_time']) - ->willReturn($ruleData['to_time']); - - $statementMock->expects($this->at(0))->method('fetch')->willReturn($ruleData); - $statementMock->expects($this->at(1))->method('fetch')->willReturn(false); - - $this->productPriceCalculatorMock->expects($this->atLeastOnce())->method('calculate'); - $this->pricesPersistorMock->expects($this->once())->method('execute'); + $this->localeDate->expects($this->once()) + ->method('scopeDate') + ->with($defaultStoreId, null, true) + ->willReturn(new \DateTime()); + + $statementMock->expects($this->at(0)) + ->method('fetch') + ->willReturn($ruleData); + $statementMock->expects($this->at(1)) + ->method('fetch') + ->willReturn(false); + + $this->productPriceCalculatorMock->expects($this->atLeastOnce()) + ->method('calculate'); + $this->pricesPersistorMock->expects($this->once()) + ->method('execute'); $this->assertTrue($this->model->execute(1, $productMock, true)); } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php index cb1a7f53f752c..7514d2bc4b5c5 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Pricing/Price/CatalogRulePriceTest.php @@ -112,6 +112,7 @@ protected function setUp() */ public function testGetValue() { + $storeId = 5; $coreWebsiteId = 2; $productId = 4; $customerGroupId = 3; @@ -120,9 +121,12 @@ public function testGetValue() $catalogRulePrice = 55.12; $convertedPrice = 45.34; + $this->coreStoreMock->expects($this->once()) + ->method('getId') + ->willReturn($storeId); $this->dataTimeMock->expects($this->once()) - ->method('date') - ->with(null, null, false) + ->method('scopeDate') + ->with($storeId) ->willReturn($date); $this->coreStoreMock->expects($this->once()) ->method('getWebsiteId') From b3b920d73cd0f007abe7d833c78a5b85b8f88b15 Mon Sep 17 00:00:00 2001 From: Vitalii Tychenok <v.tychenok@ism-ukraine.com> Date: Mon, 12 Aug 2019 10:09:36 +0300 Subject: [PATCH 192/841] Fix for Static Tests --- .../Magento/Multishipping/Controller/Checkout/Plugin.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php index dd47949ea408f..d397e628b6be8 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php @@ -1,11 +1,15 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Multishipping\Controller\Checkout; +/** + * Class Plugin + * + * @package Magento\Multishipping\Controller\Checkout + */ class Plugin { /** From 4205adc8c812abb2d4f67765a22e81502a98056e Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 12 Aug 2019 10:48:50 +0300 Subject: [PATCH 193/841] MC-18561: After changing store view the cms page is not redirecting correctly --- .../CmsUrlRewrite/etc/adminhtml/di.xml | 2 +- .../Plugin/Cms/Model/Store/ViewTest.php | 51 ++++++++++++------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml b/app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml index 5e4de51808cbd..c6b0e4b05f16b 100644 --- a/app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml +++ b/app/code/Magento/CmsUrlRewrite/etc/adminhtml/di.xml @@ -7,6 +7,6 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Store\Model\ResourceModel\Store"> - <plugin name="cms_url_rewrite_after_store_save" type="Magento\CmsUrlRewrite\Plugin\Cms\Model\Store\View"/> + <plugin name="update_cms_url_rewrites_after_store_save" type="Magento\CmsUrlRewrite\Plugin\Cms\Model\Store\View"/> </type> </config> diff --git a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php index ea7c23b05605d..de65c85a25bdd 100644 --- a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php +++ b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php @@ -10,12 +10,16 @@ use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Registry; use Magento\Store\Model\Store; +use Magento\Store\Model\StoreFactory; +use Magento\Store\Api\WebsiteRepositoryInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; /** * Test for plugin which is listening store resource model and on save replace cms page url rewrites + * + * @magentoAppArea adminhtml */ class ViewTest extends \PHPUnit\Framework\TestCase { @@ -29,6 +33,21 @@ class ViewTest extends \PHPUnit\Framework\TestCase */ private $objectManager; + /** + * @var StoreFactory + */ + private $storeFactory; + + /** + * @var string + */ + private $storeCode; + + /** + * @var WebsiteRepositoryInterface + */ + private $websiteRepository; + /** * @inheritdoc */ @@ -36,15 +55,17 @@ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); $this->urlFinder = $this->objectManager->create(UrlFinderInterface::class); + $this->storeFactory = $this->objectManager->create(StoreFactory::class); + $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); + $this->storeCode = 'test_' . mt_rand(); } /** * Test of replacing cms page url rewrites on create and delete store * * @magentoDataFixture Magento/Cms/_files/pages.php - * @magentoAppArea adminhtml */ - public function testAfterSave() + public function testUrlRewritesChangesAfterStoreSave() { $data = [ UrlRewrite::REQUEST_PATH => 'page100', @@ -67,13 +88,13 @@ public function testAfterSave() private function createStore(): void { /** @var $store Store */ - $store = $this->objectManager->create(Store::class); - if (!$store->load('test', 'code')->getId()) { + $store = $this->storeFactory->create(); + if (!$store->load($this->storeCode, 'code')->getId()) { $store->setData( [ - 'code' => 'test', - 'website_id' => '1', - 'group_id' => '1', + 'code' => $this->storeCode, + 'website_id' => $this->websiteRepository->getDefault()->getId(), + 'group_id' => $this->websiteRepository->getDefault()->getDefaultGroupId(), 'name' => 'Test Store', 'sort_order' => '0', 'is_active' => '1', @@ -83,7 +104,7 @@ private function createStore(): void } else { if ($store->getId()) { /** @var \Magento\TestFramework\Helper\Bootstrap $registry */ - $registry = Bootstrap::getObjectManager()->get( + $registry = $this->objectManager->get( Registry::class ); $registry->unregister('isSecureArea'); @@ -94,9 +115,9 @@ private function createStore(): void $store = $this->objectManager->create(Store::class); $store->setData( [ - 'code' => 'test', - 'website_id' => '1', - 'group_id' => '1', + 'code' => $this->storeCode, + 'website_id' => $this->websiteRepository->getDefault()->getId(), + 'group_id' => $this->websiteRepository->getDefault()->getDefaultGroupId(), 'name' => 'Test Store', 'sort_order' => '0', 'is_active' => '1', @@ -120,13 +141,7 @@ private function deleteStore(): void $registry->register('isSecureArea', true); /** @var Store $store */ $store = $this->objectManager->get(Store::class); - $store->load('test', 'code'); - if ($store->getId()) { - $store->delete(); - } - /** @var Store $store */ - $store = $this->objectManager->get(Store::class); - $store->load('test', 'code'); + $store->load($this->storeCode, 'code'); if ($store->getId()) { $store->delete(); } From b5353a0967434bc21287ca4153cdaf84b500c93a Mon Sep 17 00:00:00 2001 From: Vitalii Tychenok <v.tychenok@ism-ukraine.com> Date: Mon, 12 Aug 2019 10:57:42 +0300 Subject: [PATCH 194/841] Fix for Integration Tests --- .../Magento/Multishipping/Controller/Checkout/Plugin.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php index d397e628b6be8..cf1a12c352d5b 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php @@ -34,7 +34,9 @@ public function __construct(\Magento\Checkout\Model\Cart $cart) */ public function beforeExecute(\Magento\Framework\App\Action\Action $subject) { - $this->cart->getQuote()->setIsMultiShipping(0); - $this->cart->saveQuote(); + if ($this->cart->getQuote()->getIsMultiShipping()) { + $this->cart->getQuote()->setIsMultiShipping(0); + $this->cart->saveQuote(); + } } } From a86bd547d09b0187b9a7b71a60ef3127a0740bec Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 12 Aug 2019 11:19:28 +0300 Subject: [PATCH 195/841] MC-18561: After changing store view the cms page is not redirecting correctly --- .../Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index 308a465b7de90..c7e592f5b9b85 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -10,7 +10,7 @@ use Magento\Cms\Api\PageRepositoryInterface; use Magento\CmsUrlRewrite\Model\CmsPageUrlRewriteGenerator; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Store\Model\Store; +use Magento\Framework\Model\AbstractModel; use Magento\Store\Model\ResourceModel\Store as ResourceStore; use Magento\UrlRewrite\Model\UrlPersistInterface; @@ -42,6 +42,8 @@ class View private $searchCriteriaBuilder; /** + * Update store view plugin constructor + * * @param UrlPersistInterface $urlPersist * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param PageRepositoryInterface $pageRepository @@ -62,11 +64,11 @@ public function __construct( /** * @param ResourceStore $object * @param \Closure $proceed - * @param Store $store + * @param AbstractModel $store * @return mixed * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundSave(ResourceStore $object, \Closure $proceed, Store $store) + public function aroundSave(ResourceStore $object, \Closure $proceed, AbstractModel $store) { $result = $proceed($store); if ($store->isObjectNew() || $store->dataHasChangedFor('group_id')) { From eb9aa30a96ae971109eb0bb8cc95039b05cc2dcf Mon Sep 17 00:00:00 2001 From: Vitalii Tychenok <v.tychenok@ism-ukraine.com> Date: Mon, 12 Aug 2019 11:58:36 +0300 Subject: [PATCH 196/841] Fix for Unit Tests --- .../Controller/Checkout/Plugin.php | 5 +++-- .../Unit/Controller/Checkout/PluginTest.php | 18 ++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php index cf1a12c352d5b..42458f63593ad 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php @@ -34,8 +34,9 @@ public function __construct(\Magento\Checkout\Model\Cart $cart) */ public function beforeExecute(\Magento\Framework\App\Action\Action $subject) { - if ($this->cart->getQuote()->getIsMultiShipping()) { - $this->cart->getQuote()->setIsMultiShipping(0); + $quote = $this->cart->getQuote(); + if ($quote->getIsMultiShipping()) { + $quote->setIsMultiShipping(0); $this->cart->saveQuote(); } } diff --git a/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php b/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php index 1b1474dbed28a..a26f2661ebab1 100644 --- a/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php +++ b/app/code/Magento/Multishipping/Test/Unit/Controller/Checkout/PluginTest.php @@ -4,6 +4,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Multishipping\Test\Unit\Controller\Checkout; use Magento\Multishipping\Controller\Checkout\Plugin; @@ -30,16 +33,27 @@ protected function setUp() $this->cartMock = $this->createMock(\Magento\Checkout\Model\Cart::class); $this->quoteMock = $this->createPartialMock( \Magento\Quote\Model\Quote::class, - ['__wakeUp', 'setIsMultiShipping'] + ['__wakeUp', 'setIsMultiShipping', 'getIsMultiShipping'] ); $this->cartMock->expects($this->once())->method('getQuote')->will($this->returnValue($this->quoteMock)); $this->object = new \Magento\Multishipping\Controller\Checkout\Plugin($this->cartMock); } - public function testExecuteTurnsOffMultishippingModeOnQuote() + public function testExecuteTurnsOffMultishippingModeOnMultishippingQuote(): void { $subject = $this->createMock(\Magento\Checkout\Controller\Index\Index::class); + $this->quoteMock->expects($this->once())->method('getIsMultiShipping')->willReturn(1); $this->quoteMock->expects($this->once())->method('setIsMultiShipping')->with(0); + $this->cartMock->expects($this->once())->method('saveQuote'); + $this->object->beforeExecute($subject); + } + + public function testExecuteTurnsOffMultishippingModeOnNotMultishippingQuote(): void + { + $subject = $this->createMock(\Magento\Checkout\Controller\Index\Index::class); + $this->quoteMock->expects($this->once())->method('getIsMultiShipping')->willReturn(0); + $this->quoteMock->expects($this->never())->method('setIsMultiShipping'); + $this->cartMock->expects($this->never())->method('saveQuote'); $this->object->beforeExecute($subject); } } From 9140fab0b61c7c08670de22587b1452c44bbdfda Mon Sep 17 00:00:00 2001 From: Oleksandr Kravchuk <swnsma@gmail.com> Date: Mon, 12 Aug 2019 13:12:08 +0300 Subject: [PATCH 197/841] Update Plugin.php --- .../Magento/Multishipping/Controller/Checkout/Plugin.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php index 42458f63593ad..f88cdfc26fa9f 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Plugin.php @@ -3,12 +3,12 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Multishipping\Controller\Checkout; /** - * Class Plugin - * - * @package Magento\Multishipping\Controller\Checkout + * Turns Off Multishipping mode for Quote. */ class Plugin { From 8375d0815f36d47152601cb47c8cf497f1e45695 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 12 Aug 2019 14:09:43 +0300 Subject: [PATCH 198/841] MC-18561: After changing store view the cms page is not redirecting correctly --- app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php | 2 ++ .../Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index c7e592f5b9b85..c4b9655bbc7d6 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -62,6 +62,8 @@ public function __construct( } /** + * Replace cms page url rewrites on store view save + * * @param ResourceStore $object * @param \Closure $proceed * @param AbstractModel $store diff --git a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php index de65c85a25bdd..bd8bc2fffbb0e 100644 --- a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php +++ b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php @@ -57,7 +57,7 @@ protected function setUp() $this->urlFinder = $this->objectManager->create(UrlFinderInterface::class); $this->storeFactory = $this->objectManager->create(StoreFactory::class); $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); - $this->storeCode = 'test_' . mt_rand(); + $this->storeCode = 'test_' . random_int(0, 999); } /** From 406af22f5af671d0ff53b5c06cc2a882b9f2b4a4 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 12 Aug 2019 15:19:40 +0300 Subject: [PATCH 199/841] MC-18963: Bundle Product Cart Pricing issue when added a special price for associated products --- .../Plugin/UpdatePriceInQuoteItemOptions.php | 2 +- .../SpecialPriceBundleProductInCartTest.xml | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/SpecialPriceBundleProductInCartTest.xml diff --git a/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php b/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php index d5aafb8ad2b61..acff09c60522a 100644 --- a/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php +++ b/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php @@ -42,7 +42,7 @@ public function afterCalcRowTotal(OrigQuoteItem $subject, AbstractItem $result) { $bundleAttributes = $result->getProduct()->getCustomOption('bundle_selection_attributes'); if ($bundleAttributes !== null) { - $actualPrice = $result->getPrice(); + $actualPrice = (float)$result->getPrice(); $parsedValue = $this->serializer->unserialize($bundleAttributes->getValue()); if (is_array($parsedValue) && array_key_exists('price', $parsedValue)) { $parsedValue['price'] = $actualPrice; diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/SpecialPriceBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/SpecialPriceBundleProductInCartTest.xml new file mode 100644 index 0000000000000..19172ef9aca90 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/SpecialPriceBundleProductInCartTest.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="SpecialPriceBundleProductInCartTest"> + <annotations> + <features value="Bundle"/> + <stories value="Check that the cart total is correct when the bundle product with special price added to the cart"/> + <title value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> + <description value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> + <severity value="MAJOR"/> + <testCaseId value="MC-19134"/> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <createData entity="_defaultCategory" stepKey="category"/> + <!-- Create simple product and add special price --> + <createData entity="SimpleProductWithSpecialPrice" stepKey="simpleProduct"> + <requiredEntity createDataKey="category"/> + </createData> + <amOnPage url="{{AdminProductEditPage.url($simpleProduct.id$)}}" stepKey="goToAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPage"/> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"> + <argument name="price" value="{{SimpleProductWithSpecialPrice.special_price}}"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> + </before> + <after> + <!-- Delete the bundled product --> + <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="ClearFiltersAfter"/> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="logout"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + </after> + <!--Go to bundle product creation page--> + <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage"/> + <waitForPageLoad stepKey="waitForBundleProductCreatePageToLoad"/> + <actionGroup ref="fillMainBundleProductForm" stepKey="fillMainFieldsForBundle"/> + <!-- Add Option, a "Radio Buttons" type option --> + <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOptions"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="Option"/> + <argument name="inputType" value="radio"/> + </actionGroup> + <checkOption selector="{{AdminProductFormBundleSection.userDefinedQuantity('0', '0')}}" stepKey="userDefinedQuantitiyOptionProduct"/> + <actionGroup ref="saveProductForm" stepKey="saveBundleProduct"/> + <!-- Go to storefront BundleProduct --> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <actionGroup ref="StorefrontAddBundleProductFromProductToCartActionGroup" stepKey="addProductToCartFirstTime"> + <argument name="productName" value="{{BundleProduct.name}}"/> + </actionGroup> + <actionGroup ref="StorefrontAddBundleProductFromProductToCartActionGroup" stepKey="addProductToCartSecondTime"> + <argument name="productName" value="{{BundleProduct.name}}"/> + </actionGroup> + <click stepKey="openMiniCart" selector="{{StorefrontMinicartSection.showCart}}"/> + <waitForPageLoad stepKey="waitForMiniCart"/> + <see stepKey="seeCartSubtotal" userInput="$180.00"/> + </test> +</tests> From ecd3a1c64574e6abccc5f51af4cb984115eafedc Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 12 Aug 2019 16:12:58 +0300 Subject: [PATCH 200/841] MC-18561: After changing store view the cms page is not redirecting correctly --- .../Plugin/Cms/Model/Store/ViewTest.php | 103 +++++------------- 1 file changed, 30 insertions(+), 73 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php index bd8bc2fffbb0e..422cc2958e988 100644 --- a/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php +++ b/dev/tests/integration/testsuite/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/ViewTest.php @@ -8,10 +8,8 @@ namespace Magento\CmsUrlRewrite\Plugin\Cms\Model\Store; use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\Registry; use Magento\Store\Model\Store; use Magento\Store\Model\StoreFactory; -use Magento\Store\Api\WebsiteRepositoryInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; @@ -34,20 +32,10 @@ class ViewTest extends \PHPUnit\Framework\TestCase private $objectManager; /** - * @var StoreFactory + * @var Store */ private $storeFactory; - /** - * @var string - */ - private $storeCode; - - /** - * @var WebsiteRepositoryInterface - */ - private $websiteRepository; - /** * @inheritdoc */ @@ -56,8 +44,6 @@ protected function setUp() $this->objectManager = Bootstrap::getObjectManager(); $this->urlFinder = $this->objectManager->create(UrlFinderInterface::class); $this->storeFactory = $this->objectManager->create(StoreFactory::class); - $this->websiteRepository = $this->objectManager->get(WebsiteRepositoryInterface::class); - $this->storeCode = 'test_' . random_int(0, 999); } /** @@ -66,86 +52,57 @@ protected function setUp() * @magentoDataFixture Magento/Cms/_files/pages.php */ public function testUrlRewritesChangesAfterStoreSave() + { + $storeId = $this->createStore(); + $this->assertUrlRewritesCount($storeId, 1); + $this->deleteStore($storeId); + $this->assertUrlRewritesCount($storeId, 0); + } + + /** + * Assert url rewrites count by store id + * + * @param int $storeId + * @param int $expectedCount + */ + private function assertUrlRewritesCount(int $storeId, int $expectedCount): void { $data = [ UrlRewrite::REQUEST_PATH => 'page100', + UrlRewrite::STORE_ID => $storeId ]; $urlRewrites = $this->urlFinder->findAllByData($data); - $this->assertCount(1, $urlRewrites); - $this->createStore(); - $urlRewrites = $this->urlFinder->findAllByData($data); - $this->assertCount(2, $urlRewrites); - $this->deleteStore(); - $urlRewrites = $this->urlFinder->findAllByData($data); - $this->assertCount(1, $urlRewrites); + $this->assertCount($expectedCount, $urlRewrites); } /** * Create test store * - * @return void + * @return int */ - private function createStore(): void + private function createStore(): int { - /** @var $store Store */ $store = $this->storeFactory->create(); - if (!$store->load($this->storeCode, 'code')->getId()) { - $store->setData( - [ - 'code' => $this->storeCode, - 'website_id' => $this->websiteRepository->getDefault()->getId(), - 'group_id' => $this->websiteRepository->getDefault()->getDefaultGroupId(), - 'name' => 'Test Store', - 'sort_order' => '0', - 'is_active' => '1', - ] - ); - $store->save(); - } else { - if ($store->getId()) { - /** @var \Magento\TestFramework\Helper\Bootstrap $registry */ - $registry = $this->objectManager->get( - Registry::class - ); - $registry->unregister('isSecureArea'); - $registry->register('isSecureArea', true); - $store->delete(); - $registry->unregister('isSecureArea'); - $registry->register('isSecureArea', false); - $store = $this->objectManager->create(Store::class); - $store->setData( - [ - 'code' => $this->storeCode, - 'website_id' => $this->websiteRepository->getDefault()->getId(), - 'group_id' => $this->websiteRepository->getDefault()->getDefaultGroupId(), - 'name' => 'Test Store', - 'sort_order' => '0', - 'is_active' => '1', - ] - ); - $store->save(); - } - } + $store->setCode('test_' . random_int(0, 999)) + ->setName('Test Store') + ->unsId() + ->save(); + + return (int)$store->getId(); } /** * Delete test store * + * @param int $storeId * @return void */ - private function deleteStore(): void + private function deleteStore(int $storeId): void { - /** @var Registry $registry */ - $registry = $this->objectManager->get(Registry::class); - $registry->unregister('isSecureArea'); - $registry->register('isSecureArea', true); - /** @var Store $store */ - $store = $this->objectManager->get(Store::class); - $store->load($this->storeCode, 'code'); - if ($store->getId()) { + $store = $this->storeFactory->create(); + $store->load($storeId); + if ($store !== null) { $store->delete(); } - $registry->unregister('isSecureArea'); - $registry->register('isSecureArea', false); } } From 63e7e0b95e159f2107c58ceee42bd9eee02e3b75 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 12 Aug 2019 16:18:30 +0300 Subject: [PATCH 201/841] MC-18963: Bundle Product Cart Pricing issue when added a special price for associated products --- ...rontSpecialPriceBundleProductInCartTest.xml} | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) rename app/code/Magento/Bundle/Test/Mftf/Test/{SpecialPriceBundleProductInCartTest.xml => StorefrontSpecialPriceBundleProductInCartTest.xml} (91%) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/SpecialPriceBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml similarity index 91% rename from app/code/Magento/Bundle/Test/Mftf/Test/SpecialPriceBundleProductInCartTest.xml rename to app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml index 19172ef9aca90..0b6e98fed3c99 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/SpecialPriceBundleProductInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="SpecialPriceBundleProductInCartTest"> + <test name="StorefrontSpecialPriceBundleProductInCartTest"> <annotations> <features value="Bundle"/> <stories value="Check that the cart total is correct when the bundle product with special price added to the cart"/> @@ -15,35 +15,33 @@ <description value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> <severity value="MAJOR"/> <testCaseId value="MC-19134"/> - <group value="Bundle"/> + <group value="bundle"/> </annotations> <before> - <actionGroup ref="LoginAsAdmin" stepKey="login"/> <createData entity="_defaultCategory" stepKey="category"/> - <!-- Create simple product and add special price --> + <!-- Create simple product and add special price --> <createData entity="SimpleProductWithSpecialPrice" stepKey="simpleProduct"> <requiredEntity createDataKey="category"/> </createData> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> <amOnPage url="{{AdminProductEditPage.url($simpleProduct.id$)}}" stepKey="goToAdminProductPage"/> - <waitForPageLoad stepKey="waitForAdminProductPage"/> <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"> <argument name="price" value="{{SimpleProductWithSpecialPrice.special_price}}"/> </actionGroup> <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> </before> <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> <!-- Delete the bundled product --> <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> <argument name="product" value="BundleProduct"/> </actionGroup> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="ClearFiltersAfter"/> - <amOnPage url="{{AdminLogoutPage.url}}" stepKey="logout"/> - <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> - <deleteData createDataKey="category" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> </after> <!--Go to bundle product creation page--> <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage"/> - <waitForPageLoad stepKey="waitForBundleProductCreatePageToLoad"/> <actionGroup ref="fillMainBundleProductForm" stepKey="fillMainFieldsForBundle"/> <!-- Add Option, a "Radio Buttons" type option --> <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOptions"> @@ -58,7 +56,6 @@ <actionGroup ref="saveProductForm" stepKey="saveBundleProduct"/> <!-- Go to storefront BundleProduct --> <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> - <waitForPageLoad stepKey="waitForStorefront"/> <actionGroup ref="StorefrontAddBundleProductFromProductToCartActionGroup" stepKey="addProductToCartFirstTime"> <argument name="productName" value="{{BundleProduct.name}}"/> </actionGroup> From 254eb5a36b8847e465777e58b31619b81c566d7b Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 12 Aug 2019 18:34:23 +0300 Subject: [PATCH 202/841] MC-18963: Bundle Product Cart Pricing issue when added a special price for associated products --- ...ontSpecialPriceBundleProductInCartTest.xml | 48 +++++++++---------- ...tStorefrontMiniCartSubtotalActionGroup.xml | 17 +++++++ 2 files changed, 39 insertions(+), 26 deletions(-) create mode 100644 app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartSubtotalActionGroup.xml diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml index 0b6e98fed3c99..aef88cb4cf3bf 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml @@ -15,14 +15,27 @@ <description value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> <severity value="MAJOR"/> <testCaseId value="MC-19134"/> + <useCaseId value="MC-18963"/> <group value="bundle"/> </annotations> <before> <createData entity="_defaultCategory" stepKey="category"/> - <!-- Create simple product and add special price --> + <!-- Create simple product --> <createData entity="SimpleProductWithSpecialPrice" stepKey="simpleProduct"> <requiredEntity createDataKey="category"/> </createData> + <!-- Create the bundle product --> + <createData entity="ApiBundleProduct" stepKey="bundleProduct"/> + <createData entity="RadioButtonsOption" stepKey="bundleOption"> + <requiredEntity createDataKey="bundleProduct"/> + <field key="required">true</field> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="bundleProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="simpleProduct"/> + </createData> + <!-- Add special price to simple product --> <actionGroup ref="LoginAsAdmin" stepKey="login"/> <amOnPage url="{{AdminProductEditPage.url($simpleProduct.id$)}}" stepKey="goToAdminProductPage"/> <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"> @@ -31,39 +44,22 @@ <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> </before> <after> + <deleteData createDataKey="bundleProduct" stepKey="deleteBundleProduct"/> <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> <deleteData createDataKey="category" stepKey="deleteCategory"/> - <!-- Delete the bundled product --> - <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> - <argument name="product" value="BundleProduct"/> - </actionGroup> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="ClearFiltersAfter"/> <actionGroup ref="logout" stepKey="logout"/> </after> - <!--Go to bundle product creation page--> - <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage"/> - <actionGroup ref="fillMainBundleProductForm" stepKey="fillMainFieldsForBundle"/> - <!-- Add Option, a "Radio Buttons" type option --> - <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOptions"> - <argument name="x" value="0"/> - <argument name="n" value="1"/> - <argument name="prodOneSku" value="$$simpleProduct.sku$$"/> - <argument name="prodTwoSku" value=""/> - <argument name="optionTitle" value="Option"/> - <argument name="inputType" value="radio"/> - </actionGroup> - <checkOption selector="{{AdminProductFormBundleSection.userDefinedQuantity('0', '0')}}" stepKey="userDefinedQuantitiyOptionProduct"/> - <actionGroup ref="saveProductForm" stepKey="saveBundleProduct"/> <!-- Go to storefront BundleProduct --> - <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <amOnPage url="{{StorefrontProductPage.url($$bundleProduct.custom_attributes[url_key]$$)}}" stepKey="goToStorefront"/> <actionGroup ref="StorefrontAddBundleProductFromProductToCartActionGroup" stepKey="addProductToCartFirstTime"> - <argument name="productName" value="{{BundleProduct.name}}"/> + <argument name="productName" value="$$bundleProduct.name$$"/> </actionGroup> <actionGroup ref="StorefrontAddBundleProductFromProductToCartActionGroup" stepKey="addProductToCartSecondTime"> - <argument name="productName" value="{{BundleProduct.name}}"/> + <argument name="productName" value="$$bundleProduct.name$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="openMiniCart"/> + <actionGroup ref="AssertStorefrontMiniCartSubtotalActionGroup" stepKey="assertSubtotal"> + <argument name="subtotal" value="$180"/> </actionGroup> - <click stepKey="openMiniCart" selector="{{StorefrontMinicartSection.showCart}}"/> - <waitForPageLoad stepKey="waitForMiniCart"/> - <see stepKey="seeCartSubtotal" userInput="$180.00"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartSubtotalActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartSubtotalActionGroup.xml new file mode 100644 index 0000000000000..eba82860e8164 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontMiniCartSubtotalActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontMiniCartSubtotalActionGroup"> + <arguments> + <argument name="subtotal" type="string"/> + </arguments> + <waitForElementVisible selector="{{StorefrontMinicartSection.productSubTotal}}" stepKey="waitForSubtotal"/> + <see selector="{{StorefrontMinicartSection.productSubTotal}}" userInput="{{subtotal}}" stepKey="seeSubtotal"/> + </actionGroup> +</actionGroups> From e6083f28870d87f676901e3a9d1739defe1bc0c4 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Mon, 12 Aug 2019 11:29:05 -0500 Subject: [PATCH 203/841] MC-19103: Get aggregation from search and expose them to the filters resolver or field --- .../Model/Resolver/LayerFilters.php | 22 +++++----- .../Model/Resolver/Products.php | 2 +- .../Model/Resolver/Products/Query/Filter.php | 41 ++++++++++++++----- .../Model/Resolver/Products/Query/Search.php | 21 ++++++---- .../Model/Resolver/Products/SearchResult.php | 34 +++++++-------- .../Resolver/Products/SearchResultFactory.php | 10 ++--- 6 files changed, 79 insertions(+), 51 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php index 9aa632b4fafbb..6ef4e72627e82 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php @@ -30,17 +30,13 @@ class LayerFilters implements ResolverInterface /** * @param \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider - * @param \Magento\Framework\Registry $registry * @param LayerBuilder $layerBuilder */ public function __construct( \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider, - \Magento\Framework\Registry $registry, LayerBuilder $layerBuilder - ) { $this->filtersDataProvider = $filtersDataProvider; - $this->registry = $registry; $this->layerBuilder = $layerBuilder; } @@ -54,13 +50,19 @@ public function resolve( array $value = null, array $args = null ) { - if (!isset($value['layer_type'])) { + if (!isset($value['layer_type']) || !isset($value['search_result'])) { return null; } - $aggregations = $this->registry->registry('aggregations'); - /** @var StoreInterface $store */ - $store = $context->getExtensionAttributes()->getStore(); - $storeId = (int)$store->getId(); - return $this->layerBuilder->build($aggregations, $storeId); + + $aggregations = $value['search_result']->getSearchAggregation(); + + if ($aggregations) { + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + $storeId = (int)$store->getId(); + return $this->layerBuilder->build($aggregations, $storeId); + } else { + return []; + } } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 93ecf9c881548..9eca99b486df4 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -117,7 +117,7 @@ public function resolve( 'current_page' => $currentPage, 'total_pages' => $maxPages ], - //'filters' => $aggregations + 'search_result' => $searchResult, 'layer_type' => $layerType ]; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php index 62e2f0c488c6c..6771777341281 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query; +use GraphQL\Language\AST\SelectionNode; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product; @@ -79,7 +80,12 @@ public function getResult( $productArray[$product->getId()]['model'] = $product; } - return $this->searchResultFactory->create($products->getTotalCount(), $productArray); + return $this->searchResultFactory->create( + [ + 'totalCount' => $products->getTotalCount(), + 'productsSearchResult' => $productArray + ] + ); } /** @@ -99,20 +105,35 @@ private function getProductFields(ResolveInfo $info) : array if ($selection->name->value !== 'items') { continue; } + $fieldNames[] = $this->collectProductFieldNames($selection, $fieldNames); + } + } + + $fieldNames = array_merge(...$fieldNames); + + return $fieldNames; + } - foreach ($selection->selectionSet->selections as $itemSelection) { - if ($itemSelection->kind === 'InlineFragment') { - foreach ($itemSelection->selectionSet->selections as $inlineSelection) { - if ($inlineSelection->kind === 'InlineFragment') { - continue; - } - $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value); - } + /** + * Collect field names for each node in selection + * + * @param SelectionNode $selection + * @param array $fieldNames + * @return array + */ + private function collectProductFieldNames(SelectionNode $selection, array $fieldNames = []): array + { + foreach ($selection->selectionSet->selections as $itemSelection) { + if ($itemSelection->kind === 'InlineFragment') { + foreach ($itemSelection->selectionSet->selections as $inlineSelection) { + if ($inlineSelection->kind === 'InlineFragment') { continue; } - $fieldNames[] = $this->fieldTranslator->translate($itemSelection->name->value); + $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value); } + continue; } + $fieldNames[] = $this->fieldTranslator->translate($itemSelection->name->value); } return $fieldNames; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index 7a766269af602..1fdd64f7bbc68 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -63,7 +63,6 @@ class Search * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Search\Model\Search\PageSizeProvider $pageSize * @param SearchCriteriaInterfaceFactory $searchCriteriaFactory - * @param \Magento\Framework\Registry $registry */ public function __construct( SearchInterface $search, @@ -72,8 +71,7 @@ public function __construct( SearchResultFactory $searchResultFactory, \Magento\Framework\EntityManager\MetadataPool $metadataPool, \Magento\Search\Model\Search\PageSizeProvider $pageSize, - SearchCriteriaInterfaceFactory $searchCriteriaFactory, - \Magento\Framework\Registry $registry + SearchCriteriaInterfaceFactory $searchCriteriaFactory ) { $this->search = $search; $this->filterHelper = $filterHelper; @@ -82,7 +80,6 @@ public function __construct( $this->metadataPool = $metadataPool; $this->pageSizeProvider = $pageSize; $this->searchCriteriaFactory = $searchCriteriaFactory; - $this->registry = $registry; } /** @@ -98,14 +95,16 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $idField = $this->metadataPool->getMetadata( \Magento\Catalog\Api\Data\ProductInterface::class )->getIdentifierField(); + $realPageSize = $searchCriteria->getPageSize(); $realCurrentPage = $searchCriteria->getCurrentPage(); // Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround - //$pageSize = $this->pageSizeProvider->getMaxPageSize(); - $searchCriteria->setPageSize(10000); + $pageSize = $this->pageSizeProvider->getMaxPageSize(); + $searchCriteria->setPageSize($pageSize); $searchCriteria->setCurrentPage(0); $itemsResults = $this->search->search($searchCriteria); - $this->registry->register('aggregations', $itemsResults->getAggregations()); + $aggregation = $itemsResults->getAggregations(); + $ids = []; $searchIds = []; foreach ($itemsResults->getItems() as $item) { @@ -139,7 +138,13 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ } } - return $this->searchResultFactory->create($searchResult->getTotalCount(), $products); + return $this->searchResultFactory->create( + [ + 'totalCount' => $searchResult->getTotalCount(), + 'productsSearchResult' => $products, + 'searchAggregation' => $aggregation + ] + ); } /** diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php index 6e229bdc38a31..849d9065d2449 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php @@ -7,31 +7,21 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products; -use Magento\Framework\Api\SearchResultsInterface; +use Magento\Framework\Api\Search\AggregationInterface; /** * Container for a product search holding the item result and the array in the GraphQL-readable product type format. */ class SearchResult { - /** - * @var SearchResultsInterface - */ - private $totalCount; - - /** - * @var array - */ - private $productsSearchResult; + private $data; /** - * @param int $totalCount - * @param array $productsSearchResult + * @param array $data */ - public function __construct(int $totalCount, array $productsSearchResult) + public function __construct(array $data) { - $this->totalCount = $totalCount; - $this->productsSearchResult = $productsSearchResult; + $this->data = $data; } /** @@ -41,7 +31,7 @@ public function __construct(int $totalCount, array $productsSearchResult) */ public function getTotalCount() : int { - return $this->totalCount; + return $this->data['totalCount'] ?? 0; } /** @@ -51,6 +41,16 @@ public function getTotalCount() : int */ public function getProductsSearchResult() : array { - return $this->productsSearchResult; + return $this->data['productsSearchResult'] ?? []; + } + + /** + * Retrieve aggregated search results + * + * @return AggregationInterface|null + */ + public function getSearchAggregation(): ?AggregationInterface + { + return $this->data['searchAggregation'] ?? null; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php index aec9362f47c3a..479e6a3f96235 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResultFactory.php @@ -30,15 +30,15 @@ public function __construct(ObjectManagerInterface $objectManager) /** * Instantiate SearchResult * - * @param int $totalCount - * @param array $productsSearchResult + * @param array $data * @return SearchResult */ - public function create(int $totalCount, array $productsSearchResult) : SearchResult - { + public function create( + array $data + ): SearchResult { return $this->objectManager->create( SearchResult::class, - ['totalCount' => $totalCount, 'productsSearchResult' => $productsSearchResult] + ['data' => $data] ); } } From 0b47e429e64e36c048bcfd2df2b4c70fd59c4803 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 12 Aug 2019 12:56:47 -0500 Subject: [PATCH 204/841] MC-19090: Category Image from Gallery is not saved --- .../Catalog/Model/Category/Attribute/Backend/Image.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index 6a035a4681a54..073b1fa44a07e 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -125,7 +125,9 @@ public function beforeSave($object) } if ($imageName = $this->getUploadedImageName($value)) { - $imageName = $this->checkUniqueImageName($imageName); + if (!$this->fileResidesOutsideCategoryDir($object->getData($attributeName))) { + $imageName = $this->checkUniqueImageName($imageName); + } $object->setData($this->additionalData . $attributeName, $value); $object->setData($attributeName, $imageName); } elseif (!is_string($value)) { From 5b2f43e3d95a2c6d4371ed4fd46c51a86907e8e4 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 12 Aug 2019 15:34:39 -0500 Subject: [PATCH 205/841] MC-19090: Category Image from Gallery is not saved --- .../Category/Attribute/Backend/ImageTest.php | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php index dc74cdfc642e3..a76ae5244076f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php @@ -179,27 +179,19 @@ public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir() { $model = $this->setUpModelForAfterSave(); $model->setAttribute($this->attribute); - - $mediaDirectoryMock = $this->createMock(WriteInterface::class); - $this->filesystem->expects($this->once()) - ->method('getDirectoryWrite') - ->with(DirectoryList::MEDIA) - ->willReturn($mediaDirectoryMock); + $imagePath = '/pub/media/wysiwyg/test123.jpg'; $this->filesystem - ->expects($this->once()) + ->expects($this->exactly(2)) ->method('getUri') ->with(DirectoryList::MEDIA) ->willReturn('pub/media'); - $mediaDirectoryMock->expects($this->once()) - ->method('getAbsolutePath') - ->willReturn('/pub/media/wysiwyg/test123.jpg'); $object = new \Magento\Framework\DataObject( [ 'test_attribute' => [ [ 'name' => 'test123.jpg', - 'url' => '/pub/media/wysiwyg/test123.jpg', + 'url' => $imagePath, ], ], ] @@ -207,9 +199,9 @@ public function testBeforeSaveAttributeFileNameOutsideOfCategoryDir() $model->beforeSave($object); - $this->assertEquals('test123.jpg', $object->getTestAttribute()); + $this->assertEquals($imagePath, $object->getTestAttribute()); $this->assertEquals( - [['name' => '/pub/media/wysiwyg/test123.jpg', 'url' => '/pub/media/wysiwyg/test123.jpg']], + [['name' => $imagePath, 'url' => $imagePath]], $object->getData('_additional_data_test_attribute') ); } From a2cbd24e9bd4616cba837bcc12f6d89e02f3ebad Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 12 Aug 2019 18:00:57 -0500 Subject: [PATCH 206/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../CatalogRule/Model/Indexer/ReindexRuleProductPrice.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php index fb01ce82fb040..3d21b50a763eb 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php @@ -85,7 +85,9 @@ public function execute($batchCount, Product $product = null, $useAdditionalTabl $storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId()); $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true); $previousDate = (clone $currentDate)->modify('-1 day'); + $previousDate->setTime(23, 59, 59); $nextDate = (clone $currentDate)->modify('+1 day'); + $nextDate->setTime(0, 0, 0); while ($ruleData = $productsStmt->fetch()) { $ruleProductId = $ruleData['product_id']; From 0fcfffab4978b3b8e3c43f2e233442a98607e080 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Mon, 12 Aug 2019 18:19:35 -0500 Subject: [PATCH 207/841] MC-19102: Adding new search request and use search retrieve results - use filter to paginate --- .../Product/SearchCriteriaBuilder.php | 152 ++++++++++++++++++ .../Model/Resolver/Products/Query/Search.php | 50 +----- 2 files changed, 156 insertions(+), 46 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php new file mode 100644 index 0000000000000..f49db706e3aaf --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -0,0 +1,152 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product; + +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\Search\FilterGroupBuilder; +use Magento\Framework\Api\Search\SearchCriteriaInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; +use Magento\Catalog\Model\Product\Visibility; + +/** + * Build search criteria + */ +class SearchCriteriaBuilder +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var FilterBuilder + */ + private $filterBuilder; + + /** + * @var FilterGroupBuilder + */ + private $filterGroupBuilder; + + /** + * @var Builder + */ + private $builder; + /** + * @var Visibility + */ + private $visibility; + + /** + * @param Builder $builder + * @param ScopeConfigInterface $scopeConfig + * @param FilterBuilder $filterBuilder + * @param FilterGroupBuilder $filterGroupBuilder + * @param Visibility $visibility + */ + public function __construct( + Builder $builder, + ScopeConfigInterface $scopeConfig, + FilterBuilder $filterBuilder, + FilterGroupBuilder $filterGroupBuilder, + Visibility $visibility + ) { + $this->scopeConfig = $scopeConfig; + $this->filterBuilder = $filterBuilder; + $this->filterGroupBuilder = $filterGroupBuilder; + $this->builder = $builder; + $this->visibility = $visibility; + } + + /** + * Build search criteria + * + * @param array $args + * @param bool $includeAggregation + * @return SearchCriteriaInterface + */ + public function build(array $args, bool $includeAggregation): SearchCriteriaInterface + { + $searchCriteria = $this->builder->build('products', $args); + $searchCriteria->setRequestName('catalog_view_container'); + if ($includeAggregation) { + $this->preparePriceAggregation($searchCriteria); + } + + if (!empty($args['search'])) { + $this->addFilter($searchCriteria, 'search_term', $args['search']); + if (!$searchCriteria->getSortOrders()) { + $searchCriteria->setSortOrders(['_score' => \Magento\Framework\Api\SortOrder::SORT_DESC]); + } + } + + $this->addVisibilityFilter($searchCriteria, !empty($args['search']), !empty($args['filter'])); + + $searchCriteria->setCurrentPage($args['currentPage']); + $searchCriteria->setPageSize($args['pageSize']); + + return $searchCriteria; + } + + /** + * Add filter by visibility + * + * @param SearchCriteriaInterface $searchCriteria + * @param bool $isSearch + * @param bool $isFilter + */ + private function addVisibilityFilter(SearchCriteriaInterface $searchCriteria, bool $isSearch, bool $isFilter): void + { + if ($isFilter && $isSearch) { + // Index already contains products filtered by visibility: catalog, search, both + return ; + } + $visibilityIds = $isSearch + ? $this->visibility->getVisibleInSearchIds() + : $this->visibility->getVisibleInCatalogIds(); + + $this->addFilter($searchCriteria, 'visibility', $visibilityIds); + } + + /** + * Prepare price aggregation algorithm + * + * @param SearchCriteriaInterface $searchCriteria + * @return void + */ + private function preparePriceAggregation(SearchCriteriaInterface $searchCriteria): void + { + $priceRangeCalculation = $this->scopeConfig->getValue( + \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ); + if ($priceRangeCalculation) { + $this->addFilter($searchCriteria, 'price_dynamic_algorithm', $priceRangeCalculation); + } + } + + /** + * Add filter to search criteria + * + * @param SearchCriteriaInterface $searchCriteria + * @param string $field + * @param mixed $value + */ + private function addFilter(SearchCriteriaInterface $searchCriteria, string $field, $value): void + { + $filter = $this->filterBuilder + ->setField($field) + ->setValue($value) + ->create(); + $this->filterGroupBuilder->addFilter($filter); + $filterGroups = $searchCriteria->getFilterGroups(); + $filterGroups[] = $this->filterGroupBuilder->create(); + $searchCriteria->setFilterGroups($filterGroups); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index 1fdd64f7bbc68..11fb8b79f241e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -115,60 +115,18 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $searchCriteriaIds = $this->searchCriteriaFactory->create(); $filter = $this->filterHelper->generate($idField, 'in', $searchIds); $searchCriteriaIds = $this->filterHelper->add($searchCriteriaIds, $filter); - $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, true); - + $searchCriteriaIds->setSortOrders($searchCriteria->getSortOrders()); $searchCriteriaIds->setPageSize($realPageSize); $searchCriteriaIds->setCurrentPage($realCurrentPage); - $paginatedProducts = $this->paginateList($searchResult, $searchCriteriaIds); - - $products = []; - if (!isset($searchCriteria->getSortOrders()[0])) { - foreach ($paginatedProducts as $product) { - if (in_array($product[$idField], $searchIds)) { - $ids[$product[$idField]] = $product; - } - } - $products = array_filter($ids); - } else { - foreach ($paginatedProducts as $product) { - $productId = isset($product['entity_id']) ? $product['entity_id'] : $product[$idField]; - if (in_array($productId, $searchIds)) { - $products[] = $product; - } - } - } + + $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, true); return $this->searchResultFactory->create( [ 'totalCount' => $searchResult->getTotalCount(), - 'productsSearchResult' => $products, + 'productsSearchResult' => $searchResult->getProductsSearchResult(), 'searchAggregation' => $aggregation ] ); } - - /** - * Paginate an array of Ids that get pulled back in search based off search criteria and total count. - * - * @param SearchResult $searchResult - * @param SearchCriteriaInterface $searchCriteria - * @return int[] - */ - private function paginateList(SearchResult $searchResult, SearchCriteriaInterface $searchCriteria) : array - { - $length = $searchCriteria->getPageSize(); - // Search starts pages from 0 - $offset = $length * ($searchCriteria->getCurrentPage() - 1); - - if ($searchCriteria->getPageSize()) { - $maxPages = ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize()); - } else { - $maxPages = 0; - } - - if ($searchCriteria->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) { - $offset = (int)$maxPages; - } - return array_slice($searchResult->getProductsSearchResult(), $offset, $length); - } } From 5b0ef75ccbb1cd04adff985fbaa76cd342512de0 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Mon, 12 Aug 2019 21:32:30 -0500 Subject: [PATCH 208/841] MC-19102: Adding new search request and use search retrieve results - fix attribute list - fix category_ids - fix pagination - deprecation of searchCriteria to be used in the new builder --- .../Model/Resolver/Products.php | 59 +++++++++++-------- .../ProductEntityAttributesForAst.php | 37 +++++++++--- .../Model/Resolver/Products/Query/Search.php | 13 +++- .../Model/Resolver/Products/SearchResult.php | 30 ++++++++++ .../CatalogGraphQl/etc/schema.graphqls | 3 - .../Query/Resolver/Argument/AstConverter.php | 11 +--- 6 files changed, 107 insertions(+), 46 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 9eca99b486df4..10d0d10747eaf 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -16,6 +16,7 @@ use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\SearchFilter; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Catalog\Model\Layer\Resolver; +use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder; /** * Products field resolver, used for GraphQL request processing. @@ -24,6 +25,7 @@ class Products implements ResolverInterface { /** * @var Builder + * @deprecated */ private $searchCriteriaBuilder; @@ -34,30 +36,41 @@ class Products implements ResolverInterface /** * @var Filter + * @deprecated */ private $filterQuery; /** * @var SearchFilter + * @deprecated */ private $searchFilter; + /** + * @var SearchCriteriaBuilder + */ + private $searchApiCriteriaBuilder; + /** * @param Builder $searchCriteriaBuilder * @param Search $searchQuery * @param Filter $filterQuery * @param SearchFilter $searchFilter + * @param SearchCriteriaBuilder|null $searchCriteriaBuilder */ public function __construct( Builder $searchCriteriaBuilder, Search $searchQuery, Filter $filterQuery, - SearchFilter $searchFilter + SearchFilter $searchFilter, + SearchCriteriaBuilder $searchApiCriteriaBuilder = null ) { $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->searchQuery = $searchQuery; $this->filterQuery = $filterQuery; $this->searchFilter = $searchFilter; + $this->searchApiCriteriaBuilder = $searchApiCriteriaBuilder ?? + \Magento\Framework\App\ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); } /** @@ -70,41 +83,37 @@ public function resolve( array $value = null, array $args = null ) { - $searchCriteria = $this->searchCriteriaBuilder->build($field->getName(), $args); if ($args['currentPage'] < 1) { throw new GraphQlInputException(__('currentPage value must be greater than 0.')); } if ($args['pageSize'] < 1) { throw new GraphQlInputException(__('pageSize value must be greater than 0.')); } - $searchCriteria->setCurrentPage($args['currentPage']); - $searchCriteria->setPageSize($args['pageSize']); if (!isset($args['search']) && !isset($args['filter'])) { throw new GraphQlInputException( __("'search' or 'filter' input argument is required.") ); - } elseif (isset($args['search'])) { - $layerType = Resolver::CATALOG_LAYER_SEARCH; - $this->searchFilter->add($args['search'], $searchCriteria); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); - } else { - $layerType = Resolver::CATALOG_LAYER_CATEGORY; - $searchCriteria->setRequestName('catalog_view_container'); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); - } - //possible division by 0 - if ($searchCriteria->getPageSize()) { - $maxPages = ceil($searchResult->getTotalCount() / $searchCriteria->getPageSize()); - } else { - $maxPages = 0; } - $currentPage = $searchCriteria->getCurrentPage(); - if ($searchCriteria->getCurrentPage() > $maxPages && $searchResult->getTotalCount() > 0) { + //get product children fields queried + $productFields = (array)$info->getFieldSelection(1); + + $searchCriteria = $this->searchApiCriteriaBuilder->build( + $args, + isset($productFields['filters']) + ); + + $searchCriteria->setCurrentPage($args['currentPage']); + $searchCriteria->setPageSize($args['pageSize']); + + + $searchResult = $this->searchQuery->getResult($searchCriteria, $info); + + if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) { throw new GraphQlInputException( __( 'currentPage value %1 specified is greater than the %2 page(s) available.', - [$currentPage, $maxPages] + [$searchResult->getCurrentPage(), $searchResult->getTotalPages()] ) ); } @@ -113,12 +122,12 @@ public function resolve( 'total_count' => $searchResult->getTotalCount(), 'items' => $searchResult->getProductsSearchResult(), 'page_info' => [ - 'page_size' => $searchCriteria->getPageSize(), - 'current_page' => $currentPage, - 'total_pages' => $maxPages + 'page_size' => $searchResult->getPageSize(), + 'current_page' => $searchResult->getCurrentPage(), + 'total_pages' => $searchResult->getTotalPages() ], 'search_result' => $searchResult, - 'layer_type' => $layerType + 'layer_type' => isset($args['search']) ? Resolver::CATALOG_LAYER_SEARCH : Resolver::CATALOG_LAYER_CATEGORY, ]; return $data; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php index a547f63b217fe..17409210808cc 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php @@ -23,24 +23,41 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface private $config; /** + * Additional attributes that are not retrieved by getting fields from ProductInterface + * * @var array */ private $additionalAttributes = ['min_price', 'max_price', 'category_id']; + /** + * Array to translate graphql field to internal entity attribute + * + * @var array + */ + private $translatedAttributes = ['category_id' => 'category_ids']; + /** * @param ConfigInterface $config - * @param array $additionalAttributes + * @param string[] $additionalAttributes + * @param array $translatedAttributes */ public function __construct( ConfigInterface $config, - array $additionalAttributes = [] + array $additionalAttributes = [], + array $translatedAttributes = [] ) { $this->config = $config; $this->additionalAttributes = array_merge($this->additionalAttributes, $additionalAttributes); + $this->translatedAttributes = array_merge($this->translatedAttributes, $translatedAttributes); } /** - * {@inheritdoc} + * @inheritdoc + * + * Gather all the product entity attributes that can be filtered by search criteria. + * Example format ['attributeNameInGraphQl' => ['type' => 'String'. 'fieldName' => 'attributeNameInSearchCriteria']] + * + * @return array */ public function getEntityAttributes() : array { @@ -55,14 +72,20 @@ public function getEntityAttributes() : array $configElement = $this->config->getConfigElement($interface['interface']); foreach ($configElement->getFields() as $field) { - $fields[$field->getName()] = 'String'; + $fields[$field->getName()] = [ + 'type' => 'String', + 'fieldName' => $this->translatedAttributes[$field->getName()] ?? $field->getName(), + ]; } } - foreach ($this->additionalAttributes as $attribute) { - $fields[$attribute] = 'String'; + foreach ($this->additionalAttributes as $attributeName) { + $fields[$attributeName] = [ + 'type' => 'String', + 'fieldName' => $this->translatedAttributes[$attributeName] ?? $attributeName, + ]; } - return array_keys($fields); + return $fields; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index 11fb8b79f241e..daa3619d17b83 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -103,7 +103,6 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $searchCriteria->setPageSize($pageSize); $searchCriteria->setCurrentPage(0); $itemsResults = $this->search->search($searchCriteria); - $aggregation = $itemsResults->getAggregations(); $ids = []; $searchIds = []; @@ -121,11 +120,21 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, true); + //possible division by 0 + if ($realPageSize) { + $maxPages = (int)ceil($searchResult->getTotalCount() / $realPageSize); + } else { + $maxPages = 0; + } + return $this->searchResultFactory->create( [ 'totalCount' => $searchResult->getTotalCount(), 'productsSearchResult' => $searchResult->getProductsSearchResult(), - 'searchAggregation' => $aggregation + 'searchAggregation' => $itemsResults->getAggregations(), + 'pageSize' => $realPageSize, + 'currentPage' => $realCurrentPage, + 'totalPages' => $maxPages, ] ); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php index 849d9065d2449..e4a137413b4c5 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/SearchResult.php @@ -53,4 +53,34 @@ public function getSearchAggregation(): ?AggregationInterface { return $this->data['searchAggregation'] ?? null; } + + /** + * Retrieve the page size for the search + * + * @return int + */ + public function getPageSize(): int + { + return $this->data['pageSize'] ?? 0; + } + + /** + * Retrieve the current page for the search + * + * @return int + */ + public function getCurrentPage(): int + { + return $this->data['currentPage'] ?? 0; + } + + /** + * Retrieve total pages for the search + * + * @return int + */ + public function getTotalPages(): int + { + return $this->data['totalPages'] ?? 0; + } } diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 1ce46cf3cbef9..ea56faf94408e 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -315,9 +315,6 @@ input ProductFilterInput @doc(description: "ProductFilterInput defines the filte country_of_manufacture: FilterTypeInput @doc(description: "The product's country of origin.") custom_layout: FilterTypeInput @doc(description: "The name of a custom layout.") gift_message_available: FilterTypeInput @doc(description: "Indicates whether a gift message is available.") - visibility: FilterTypeInput @doc(description: "CATEGORY attribute name") - price_dynamic_algorithm: FilterTypeInput @doc(description: "CATEGORY attribute name") - category_ids: FilterTypeInput @doc(description: "CATEGORY attribute name") or: ProductFilterInput @doc(description: "The keyword required to perform a logical OR comparison.") } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php index 802d46eafcb97..baf165b0298c3 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/AstConverter.php @@ -59,17 +59,10 @@ public function __construct( public function getClausesFromAst(string $fieldName, array $arguments) : array { $attributes = $this->fieldEntityAttributesPool->getEntityAttributesForEntityFromField($fieldName); - $attributes[] = 'cat'; - $attributes[] = 'mysize'; - $attributes[] = 'mycolor'; - $attributes[] = 'ca_1_631447041'; - $attributes[] = 'attributeset2attribute1'; - $attributes[] = 'price_dynamic_algorithm'; - $attributes[] = 'visibility'; - $attributes[] = 'category_ids'; $conditions = []; foreach ($arguments as $argumentName => $argument) { - if (in_array($argumentName, $attributes)) { + if (key_exists($argumentName, $attributes)) { + $argumentName = $attributes[$argumentName]['fieldName'] ?? $argumentName; foreach ($argument as $clauseType => $clause) { if (is_array($clause)) { $value = []; From 2693e31ae88860a8358103e3e2c8be252ab2f066 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Tue, 13 Aug 2019 13:22:33 +0300 Subject: [PATCH 209/841] MC-18963: Bundle Product Cart Pricing issue when added a special price for associated products --- ...ontSpecialPriceBundleProductInCartTest.xml | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml index aef88cb4cf3bf..44ac68a2759f3 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSpecialPriceBundleProductInCartTest.xml @@ -10,7 +10,7 @@ <test name="StorefrontSpecialPriceBundleProductInCartTest"> <annotations> <features value="Bundle"/> - <stories value="Check that the cart total is correct when the bundle product with special price added to the cart"/> + <stories value="Add bundle product to cart on storefront"/> <title value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> <description value="Customer should not be able to add a Bundle Product to the cart when added a special price for associated products"/> <severity value="MAJOR"/> @@ -19,10 +19,10 @@ <group value="bundle"/> </annotations> <before> - <createData entity="_defaultCategory" stepKey="category"/> - <!-- Create simple product --> - <createData entity="SimpleProductWithSpecialPrice" stepKey="simpleProduct"> - <requiredEntity createDataKey="category"/> + <!-- Create the Simple product with Special price --> + <createData entity="SimpleProduct2" stepKey="simpleProduct"/> + <createData entity="specialProductPrice2" stepKey="specialPriceToSimpleProduct"> + <requiredEntity createDataKey="simpleProduct"/> </createData> <!-- Create the bundle product --> <createData entity="ApiBundleProduct" stepKey="bundleProduct"/> @@ -35,19 +35,12 @@ <requiredEntity createDataKey="bundleOption"/> <requiredEntity createDataKey="simpleProduct"/> </createData> - <!-- Add special price to simple product --> - <actionGroup ref="LoginAsAdmin" stepKey="login"/> - <amOnPage url="{{AdminProductEditPage.url($simpleProduct.id$)}}" stepKey="goToAdminProductPage"/> - <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="addSpecialPrice"> - <argument name="price" value="{{SimpleProductWithSpecialPrice.special_price}}"/> - </actionGroup> - <actionGroup ref="saveProductForm" stepKey="saveSimpleProduct"/> + <!-- Run reindex stock status --> + <magentoCLI command="indexer:reindex" arguments="cataloginventory_stock" stepKey="reindex"/> </before> <after> <deleteData createDataKey="bundleProduct" stepKey="deleteBundleProduct"/> <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> - <deleteData createDataKey="category" stepKey="deleteCategory"/> - <actionGroup ref="logout" stepKey="logout"/> </after> <!-- Go to storefront BundleProduct --> <amOnPage url="{{StorefrontProductPage.url($$bundleProduct.custom_attributes[url_key]$$)}}" stepKey="goToStorefront"/> @@ -59,7 +52,7 @@ </actionGroup> <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="openMiniCart"/> <actionGroup ref="AssertStorefrontMiniCartSubtotalActionGroup" stepKey="assertSubtotal"> - <argument name="subtotal" value="$180"/> + <argument name="subtotal" value="$111.10"/> </actionGroup> </test> </tests> From 2df1ba3712b841c334374b123d311e2841111662 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 13 Aug 2019 09:08:22 -0500 Subject: [PATCH 210/841] MC-19102: Adding new search request and use search retrieve results - Add new search_request types for graphql product search --- .../Product/SearchCriteriaBuilder.php | 4 +- .../Model/Resolver/Products.php | 3 +- .../Plugin/Search/Request/ConfigReader.php | 221 ++++++++++++++++++ app/code/Magento/CatalogGraphQl/etc/di.xml | 4 + .../CatalogGraphQl/etc/search_request.xml | 90 +++++++ 5 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php create mode 100644 app/code/Magento/CatalogGraphQl/etc/search_request.xml diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index f49db706e3aaf..6970c13aecbc6 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -74,7 +74,9 @@ public function __construct( public function build(array $args, bool $includeAggregation): SearchCriteriaInterface { $searchCriteria = $this->builder->build('products', $args); - $searchCriteria->setRequestName('catalog_view_container'); + $searchCriteria->setRequestName( + $includeAggregation ? 'graphql_product_search_with_aggregation' : 'graphql_product_search' + ); if ($includeAggregation) { $this->preparePriceAggregation($searchCriteria); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 10d0d10747eaf..9c8d3266c4d2d 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -56,7 +56,7 @@ class Products implements ResolverInterface * @param Search $searchQuery * @param Filter $filterQuery * @param SearchFilter $searchFilter - * @param SearchCriteriaBuilder|null $searchCriteriaBuilder + * @param SearchCriteriaBuilder|null $searchApiCriteriaBuilder */ public function __construct( Builder $searchCriteriaBuilder, @@ -106,7 +106,6 @@ public function resolve( $searchCriteria->setCurrentPage($args['currentPage']); $searchCriteria->setPageSize($args['pageSize']); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) { diff --git a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php new file mode 100644 index 0000000000000..70e312ff4e2ee --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php @@ -0,0 +1,221 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogGraphQl\Plugin\Search\Request; + +use Magento\Catalog\Api\Data\EavAttributeInterface; +use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; +use Magento\CatalogSearch\Model\Search\RequestGenerator; +use Magento\CatalogSearch\Model\Search\RequestGenerator\GeneratorResolver; +use Magento\Framework\Search\Request\FilterInterface; +use Magento\Framework\Search\Request\QueryInterface; + +/** + * Add search request configuration to config for give ability filter and search products during GraphQL request + * Add 2 request name with and without aggregation correspondingly: + * - graphql_product_search_with_aggregation + * - graphql_product_search + * + * @see Magento/CatalogGraphQl/etc/search_request.xml + */ +class ConfigReader +{ + /** + * @var string + */ + private $requestNameWithAggregation = 'graphql_product_search_with_aggregation'; + + /** + * @var string + */ + private $requestName = 'graphql_product_search'; + + /** + * @var DataProvider + */ + private $dataProvider; + + /** + * @var GeneratorResolver + */ + private $generatorResolver; + + /** Bucket name suffix */ + private const BUCKET_SUFFIX = '_bucket'; + + /** + * @param DataProvider $dataProvider + * @param GeneratorResolver $generatorResolver + */ + public function __construct(DataProvider $dataProvider, GeneratorResolver $generatorResolver) + { + $this->dataProvider = $dataProvider; + $this->generatorResolver = $generatorResolver; + } + + /** + * Merge reader's value with generated + * + * @param \Magento\Framework\Config\ReaderInterface $subject + * @param array $result + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterRead( + \Magento\Framework\Config\ReaderInterface $subject, + array $result + ) { + $searchRequestNameWithAggregation = $this->generateRequest(); + $searchRequest = $searchRequestNameWithAggregation; + $searchRequest['queries'][$this->requestName] = $searchRequest['queries'][$this->requestNameWithAggregation]; + unset($searchRequest['queries'][$this->requestNameWithAggregation], $searchRequest['aggregations']); + + return array_merge_recursive( + $result, + [ + $this->requestNameWithAggregation => $searchRequestNameWithAggregation, + $this->requestName => $searchRequest, + ] + ); + } + + /** + * Retrieve searchable attributes + * + * @return \Magento\Eav\Model\Entity\Attribute[] + */ + private function getSearchableAttributes(): array + { + $attributes = []; + foreach ($this->dataProvider->getSearchableAttributes() as $attribute) { + $attributes[$attribute->getAttributeCode()] = $attribute; + } + return $attributes; + } + + /** + * Generate search request for search products via GraphQL + * + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function generateRequest() + { + $request = []; + foreach ($this->getSearchableAttributes() as $attribute) { + if (\in_array($attribute->getAttributeCode(), ['price', 'visibility', 'category_ids'])) { + //same fields have special semantics + continue; + } + $queryName = $attribute->getAttributeCode() . '_query'; + $request['queries'][$this->requestNameWithAggregation]['queryReference'][] = [ + 'clause' => 'must', + 'ref' => $queryName, + ]; + switch ($attribute->getBackendType()) { + case 'static': + case 'text': + case 'varchar': + if ($attribute->getFrontendInput() === 'multiselect') { + $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX; + $request['queries'][$queryName] = [ + 'name' => $queryName, + 'type' => QueryInterface::TYPE_FILTER, + 'filterReference' => [ + [ + 'ref' => $filterName, + ], + ], + ]; + $request['filters'][$filterName] = [ + 'type' => FilterInterface::TYPE_TERM, + 'name' => $filterName, + 'field' => $attribute->getAttributeCode(), + 'value' => '$' . $attribute->getAttributeCode() . '$', + ]; + } else { + $request['queries'][$queryName] = [ + 'name' => $queryName, + 'type' => 'matchQuery', + 'value' => '$' . $attribute->getAttributeCode() . '$', + 'match' => [ + [ + 'field' => $attribute->getAttributeCode(), + 'boost' => $attribute->getSearchWeight() ?: 1, + ], + ], + ]; + } + break; + case 'decimal': + case 'datetime': + case 'date': + $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX; + $request['queries'][$queryName] = [ + 'name' => $queryName, + 'type' => QueryInterface::TYPE_FILTER, + 'filterReference' => [ + [ + 'ref' => $filterName, + ], + ], + ]; + $request['filters'][$filterName] = [ + 'field' => $attribute->getAttributeCode(), + 'name' => $filterName, + 'type' => FilterInterface::TYPE_RANGE, + 'from' => '$' . $attribute->getAttributeCode() . '.from$', + 'to' => '$' . $attribute->getAttributeCode() . '.to$', + ]; + break; + default: + $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX; + $request['queries'][$queryName] = [ + 'name' => $queryName, + 'type' => QueryInterface::TYPE_FILTER, + 'filterReference' => [ + [ + 'ref' => $filterName, + ], + ], + ]; + $request['filters'][$filterName] = [ + 'type' => FilterInterface::TYPE_TERM, + 'name' => $filterName, + 'field' => $attribute->getAttributeCode(), + 'value' => '$' . $attribute->getAttributeCode() . '$', + ]; + } + $generator = $this->generatorResolver->getGeneratorForType($attribute->getBackendType()); + + if ($attribute->getData(EavAttributeInterface::IS_FILTERABLE)) { + $bucketName = $attribute->getAttributeCode() . self::BUCKET_SUFFIX; + $request['aggregations'][$bucketName] = $generator->getAggregationData($attribute, $bucketName); + } + + $this->addSearchAttributeToFullTextSearch($attribute, $request); + } + + return $request; + } + + /** + * Add attribute with specified boost to "search" query used in full text search + * + * @param \Magento\Eav\Model\Entity\Attribute $attribute + * @param array $request + * @return void + */ + private function addSearchAttributeToFullTextSearch(\Magento\Eav\Model\Entity\Attribute $attribute, &$request): void + { + // Match search by custom price attribute isn't supported + if ($attribute->getFrontendInput() !== 'price') { + $request['queries']['search']['match'][] = [ + 'field' => $attribute->getAttributeCode(), + 'boost' => $attribute->getSearchWeight() ?: 1, + ]; + } + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml index cea1c7ce327e2..0fe30eb0503ea 100644 --- a/app/code/Magento/CatalogGraphQl/etc/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/di.xml @@ -57,4 +57,8 @@ <argument name="searchCriteriaApplier" xsi:type="object">Magento\Catalog\Model\Api\SearchCriteria\ProductCollectionProcessor</argument> </arguments> </type> + + <type name="Magento\Framework\Search\Request\Config\FilesystemReader"> + <plugin name="productAttributesDynamicFields" type="Magento\CatalogGraphQl\Plugin\Search\Request\ConfigReader" /> + </type> </config> diff --git a/app/code/Magento/CatalogGraphQl/etc/search_request.xml b/app/code/Magento/CatalogGraphQl/etc/search_request.xml new file mode 100644 index 0000000000000..ab1eea9eb6fda --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/etc/search_request.xml @@ -0,0 +1,90 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<requests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Search/etc/search_request.xsd"> + <!-- Request schema for product search including aggregation --> + <request query="graphql_product_search_with_aggregation" index="catalogsearch_fulltext"> + <dimensions> + <dimension name="scope" value="default"/> + </dimensions> + <queries> + <query xsi:type="boolQuery" name="graphql_product_search_with_aggregation" boost="1"> + <queryReference clause="should" ref="search" /> + <queryReference clause="must" ref="category"/> + <queryReference clause="must" ref="price"/> + <queryReference clause="must" ref="visibility"/> + </query> + <query xsi:type="matchQuery" value="$search_term$" name="search"> + <match field="sku"/> + <match field="*"/> + </query> + <query name="category" xsi:type="filteredQuery"> + <filterReference clause="must" ref="category_filter"/> + </query> + <query name="price" xsi:type="filteredQuery"> + <filterReference clause="must" ref="price_filter"/> + </query> + <query name="visibility" xsi:type="filteredQuery"> + <filterReference clause="must" ref="visibility_filter"/> + </query> + </queries> + <filters> + <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_id$"/> + <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/> + <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> + </filters> + <aggregations> + <bucket name="price_bucket" field="price" xsi:type="dynamicBucket" method="$price_dynamic_algorithm$"> + <metrics> + <metric type="count"/> + </metrics> + </bucket> + <bucket name="category_bucket" field="category_ids" xsi:type="termBucket"> + <metrics> + <metric type="count"/> + </metrics> + </bucket> + </aggregations> + <from>0</from> + <size>10000</size> + </request> + <!-- Request schema for product search excluding aggregation --> + <request query="graphql_product_search" index="catalogsearch_fulltext"> + <dimensions> + <dimension name="scope" value="default"/> + </dimensions> + <queries> + <query xsi:type="boolQuery" name="graphql_product_search" boost="1"> + <queryReference clause="should" ref="search" /> + <queryReference clause="must" ref="category"/> + <queryReference clause="must" ref="price"/> + <queryReference clause="must" ref="visibility"/> + </query> + <query xsi:type="matchQuery" value="$search_term$" name="search"> + <match field="sku"/> + <match field="*"/> + </query> + <query name="category" xsi:type="filteredQuery"> + <filterReference clause="must" ref="category_filter"/> + </query> + <query name="price" xsi:type="filteredQuery"> + <filterReference clause="must" ref="price_filter"/> + </query> + <query name="visibility" xsi:type="filteredQuery"> + <filterReference clause="must" ref="visibility_filter"/> + </query> + </queries> + <filters> + <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_id$"/> + <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/> + <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> + </filters> + <from>0</from> + <size>10000</size> + </request> +</requests> From 8d9424cbd35f134a15f4ab323a4e913d5fc15482 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 13 Aug 2019 12:56:54 -0500 Subject: [PATCH 211/841] MC-18512: Dynamically inject all searchable custom attributes for product filtering --- .../CatalogGraphQl/Model/Config/FilterAttributeReader.php | 4 ++++ .../CatalogGraphQl/Model/Config/SortAttributeReader.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php index b84c18ea5ac0b..1dd218329112c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php @@ -67,6 +67,10 @@ public function read($scope = null) : array $config = []; foreach ($this->getAttributeCollection() as $attribute) { + if (!$attribute->getIsUserDefined()) { + //do not override fields defined in schema.graphqls + continue; + } $attributeCode = $attribute->getAttributeCode(); foreach ($typeNames as $typeName) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php index 215b28be0579c..079084e95b9de 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php @@ -61,6 +61,10 @@ public function read($scope = null) : array $attributes = $this->attributesCollection->addSearchableAttributeFilter()->addFilter('used_for_sort_by', 1); /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ foreach ($attributes as $attribute) { + if (!$attribute->getIsUserDefined()) { + //do not override fields defined in schema.graphqls + continue; + } $attributeCode = $attribute->getAttributeCode(); $attributeLabel = $attribute->getDefaultFrontendLabel(); foreach ($map as $type) { From f326e0789e937f13cbb45fed0a5597ca6548a984 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Tue, 13 Aug 2019 13:39:25 -0500 Subject: [PATCH 212/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - added data fixture --- .../attribute_set_based_on_default_set.php | 26 ++++ ...th_layered_navigation_custom_attribute.php | 144 ++++++++++++++++++ ...d_navigation_custom_attribute_rollback.php | 46 ++++++ 3 files changed, 216 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_based_on_default_set.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_based_on_default_set.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_based_on_default_set.php new file mode 100644 index 0000000000000..929b88367dd78 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/attribute_set_based_on_default_set.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var $product \Magento\Catalog\Model\Product */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */ +$attributeSet = $objectManager->create(\Magento\Eav\Model\Entity\Attribute\Set::class); + +$entityType = $objectManager->create(\Magento\Eav\Model\Entity\Type::class)->loadByCode('catalog_product'); +$defaultSetId = $objectManager->create(\Magento\Catalog\Model\Product::class)->getDefaultAttributeSetid(); + +$data = [ + 'attribute_set_name' => 'second_attribute_set', + 'entity_type_id' => $entityType->getId(), + 'sort_order' => 200, +]; + +$attributeSet->setData($data); +$attributeSet->validate(); +$attributeSet->save(); +$attributeSet->initFromSkeleton($defaultSetId); +$attributeSet->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php new file mode 100644 index 0000000000000..00678ae904def --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../Catalog/_files/attribute_set_based_on_default_set.php'; +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../Catalog/_files/categories.php'; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\TestFramework\Helper\CacheCleaner; + +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + +$eavConfig->clear(); + +$attribute1 = $eavConfig->getAttribute('catalog_product', ' second_test_configurable'); +$eavConfig->clear(); + +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = Bootstrap::getObjectManager()->create(\Magento\Catalog\Setup\CategorySetup::class); + +if (!$attribute->getId()) { + + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + ); + + /** @var AttributeRepositoryInterface $attributeRepository */ + $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); + + $attribute->setData( + [ + 'attribute_code' => 'test_configurable', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'select', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_comparable' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 1, + 'used_in_product_listing' => 1, + 'used_for_sort_by' => 1, + 'frontend_label' => ['Test Configurable'], + 'backend_type' => 'int', + 'option' => [ + 'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']], + 'order' => ['option_0' => 1, 'option_1' => 2], + ], + 'default' => ['option_0'] + ] + ); + + $attributeRepository->save($attribute); + + /* Assign attribute to attribute set */ + $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); + CacheCleaner::cleanAll(); +} +// create a second attribute +if (!$attribute1->getId()) { + + /** @var $attribute1 \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute1 = Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + ); + + /** @var AttributeRepositoryInterface $attributeRepository */ + $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); + + $attribute1->setData( + [ + 'attribute_code' => 'second_test_configurable', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'select', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_comparable' => 1, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 1, + 'used_in_product_listing' => 1, + 'used_for_sort_by' => 1, + 'frontend_label' => ['Second Test Configurable'], + 'backend_type' => 'int', + 'option' => [ + 'value' => ['option_0' => ['Option 3'], 'option_1' => ['Option 4']], + 'order' => ['option_0' => 1, 'option_1' => 2], + ], + 'default' => ['option_0'] + ] + ); + + $attributeRepository->save($attribute1); + + /* Assign attribute to attribute set */ + $installer->addAttributeToGroup('catalog_product', $attributeSet->getId(), $attributeSet->getDefaultGroupId(), $attribute1->getId()); + CacheCleaner::cleanAll(); +} + +$eavConfig->clear(); + +/** @var \Magento\Framework\ObjectManagerInterface $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $productRepository \Magento\Catalog\Api\ProductRepositoryInterface */ +$productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productsWithNewAttributeSet = ['simple', 'simple-4']; + +foreach ($productsWithNewAttributeSet as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + $product->setAttributeSetId($attributeSet->getId()); + $productRepository->save($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + + } +} +/** @var \Magento\Indexer\Model\Indexer\Collection $indexerCollection */ +$indexerCollection = Bootstrap::getObjectManager()->get(\Magento\Indexer\Model\Indexer\Collection::class); +$indexerCollection->load(); +/** @var \Magento\Indexer\Model\Indexer $indexer */ +foreach ($indexerCollection->getItems() as $indexer) { + $indexer->reindexAll(); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php new file mode 100644 index 0000000000000..4212075c312c9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../Eav/_files/empty_attribute_set_rollback.php'; +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../Catalog/_files/categories_rollback.php'; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\TestFramework\Helper\CacheCleaner; + +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$attributesToDelete = ['test_configurable', 'second_test_configurable']; +/** @var AttributeRepositoryInterface $attributeRepository */ +$attributeRepository = Bootstrap::getObjectManager()->get(AttributeRepositoryInterface::class); + +foreach ($attributesToDelete as $attributeCode) { + /** @var \Magento\Eav\Api\Data\AttributeInterface $attribute */ + $attribute = $attributeRepository->get('catalog_product', $attributeCode); + $attributeRepository->delete($attribute); +} +/** @var $product \Magento\Catalog\Model\Product */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +$entityType = $objectManager->create(\Magento\Eav\Model\Entity\Type::class)->loadByCode('catalog_product'); + +// remove attribute set + +/** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection $attributeSetCollection */ +$attributeSetCollection = $objectManager->create( + \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection::class +); +$attributeSetCollection->addFilter('attribute_set_name', 'second_attribute_set'); +$attributeSetCollection->addFilter('entity_type_id', $entityType->getId()); +$attributeSetCollection->setOrder('attribute_set_id'); // descending is default value +$attributeSetCollection->setPageSize(1); +$attributeSetCollection->load(); + +/** @var \Magento\Eav\Model\Entity\Attribute\Set $attributeSet */ +$attributeSet = $attributeSetCollection->fetchItem(); +$attributeSet->delete(); From b92cd06916cb62447fe11501a7dbafb7b866566d Mon Sep 17 00:00:00 2001 From: Nikita Chubukov <nikita_chubukov@epam.com> Date: Tue, 13 Aug 2019 21:52:06 +0300 Subject: [PATCH 213/841] MC-15256: Exported customer without modification can not be imported - Fix CR comments --- 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 33dc7e55228bf..914b4fd2a3ca9 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -608,7 +608,7 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber) } if (isset($rowData[$attributeCode]) && strlen((string)$rowData[$attributeCode])) { - if ($attributeParams['type'] == 'select' && empty($rowData[$attributeCode])) { + if ($attributeParams['type'] == 'select') { continue; } From 8225526e4e6223fd7d1e2e4ae8ea9fcde54e58ab Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Tue, 13 Aug 2019 14:34:53 -0500 Subject: [PATCH 214/841] Add void strict type to new model --- app/code/Magento/Widget/Model/DeleteWidgetById.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Widget/Model/DeleteWidgetById.php b/app/code/Magento/Widget/Model/DeleteWidgetById.php index bdebaa69fa531..1c27e6e1e9ed3 100644 --- a/app/code/Magento/Widget/Model/DeleteWidgetById.php +++ b/app/code/Magento/Widget/Model/DeleteWidgetById.php @@ -46,7 +46,7 @@ public function __construct( * @return void * @throws \Exception */ - public function execute(int $instanceId) + public function execute(int $instanceId) : void { $model = $this->getWidgetById($instanceId); From 0af4c6c425cf2e1b7dbebbc1ad2aee753a3ead29 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 13 Aug 2019 16:08:26 -0500 Subject: [PATCH 215/841] MC-19102: Adding new search request and use search retrieve results --- .../Product/SearchCriteriaBuilder.php | 28 +++++++++++++++++-- .../CatalogGraphQl/etc/search_request.xml | 4 +-- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index 6970c13aecbc6..9e381307d8139 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -10,9 +10,11 @@ use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\FilterGroupBuilder; use Magento\Framework\Api\Search\SearchCriteriaInterface; +use Magento\Framework\Api\SortOrder; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\Builder; use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\Api\SortOrderBuilder; /** * Build search criteria @@ -43,25 +45,33 @@ class SearchCriteriaBuilder */ private $visibility; + /** + * @var SortOrderBuilder + */ + private $sortOrderBuilder; + /** * @param Builder $builder * @param ScopeConfigInterface $scopeConfig * @param FilterBuilder $filterBuilder * @param FilterGroupBuilder $filterGroupBuilder * @param Visibility $visibility + * @param SortOrderBuilder $sortOrderBuilder */ public function __construct( Builder $builder, ScopeConfigInterface $scopeConfig, FilterBuilder $filterBuilder, FilterGroupBuilder $filterGroupBuilder, - Visibility $visibility + Visibility $visibility, + SortOrderBuilder $sortOrderBuilder ) { $this->scopeConfig = $scopeConfig; $this->filterBuilder = $filterBuilder; $this->filterGroupBuilder = $filterGroupBuilder; $this->builder = $builder; $this->visibility = $visibility; + $this->sortOrderBuilder = $sortOrderBuilder; } /** @@ -84,7 +94,7 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte if (!empty($args['search'])) { $this->addFilter($searchCriteria, 'search_term', $args['search']); if (!$searchCriteria->getSortOrders()) { - $searchCriteria->setSortOrders(['_score' => \Magento\Framework\Api\SortOrder::SORT_DESC]); + $this->addDefaultSortOrder($searchCriteria); } } @@ -151,4 +161,18 @@ private function addFilter(SearchCriteriaInterface $searchCriteria, string $fiel $filterGroups[] = $this->filterGroupBuilder->create(); $searchCriteria->setFilterGroups($filterGroups); } + + /** + * Sort by _score DESC if no sort order is set + * + * @param SearchCriteriaInterface $searchCriteria + */ + private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria): void + { + $sortOrder = $this->sortOrderBuilder + ->setField('_score') + ->setDirection(SortOrder::SORT_DESC) + ->create(); + $searchCriteria->setSortOrders([$sortOrder]); + } } diff --git a/app/code/Magento/CatalogGraphQl/etc/search_request.xml b/app/code/Magento/CatalogGraphQl/etc/search_request.xml index ab1eea9eb6fda..5e962d8467a4f 100644 --- a/app/code/Magento/CatalogGraphQl/etc/search_request.xml +++ b/app/code/Magento/CatalogGraphQl/etc/search_request.xml @@ -34,7 +34,7 @@ </query> </queries> <filters> - <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_id$"/> + <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/> <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/> <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> @@ -80,7 +80,7 @@ </query> </queries> <filters> - <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_id$"/> + <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/> <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/> <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> From 39eb1e82a3cae66401f8cac96e749b9d4410b930 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Wed, 14 Aug 2019 11:09:18 +0300 Subject: [PATCH 216/841] MC-15341: Default product numbers to display results in poor display on Desktop - Updated automated Test script --- .../Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml index e5f05f1ea00c1..cc8a31cc8034e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml @@ -10,7 +10,7 @@ <test name="CheckDefaultNumbersProductsToDisplayTest"> <annotations> <features value="Catalog"/> - <stories value="Default product numbers to display in grid"/> + <stories value="Product grid"/> <title value="Check default numbers: products to display"/> <description value="Check default numbers: products to display"/> <severity value="MAJOR"/> From 01b5c9d14b2aeeb0a4b6719d95ef781afdd118a8 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 14 Aug 2019 13:31:30 +0300 Subject: [PATCH 217/841] MC-19105: One item purchased turns into two items on reorder (Multi-shipping) --- .../Magento/Multishipping/Model/Checkout/Type/Multishipping.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php index 42f5289d2109a..0d6c8517320ce 100644 --- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php +++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php @@ -716,7 +716,7 @@ protected function _prepareOrder(\Magento\Quote\Model\Quote\Address $address) ); $orderItem = $this->quoteItemToOrderItem->convert($item); if ($item->getParentItem()) { - $orderItem->setParentItem($order->getItemByQuoteItemId($item->getParentItem()->getId())); + $orderItem->setParentItem($order->getItemByQuoteItemId($_quoteItem->getParentItem()->getId())); } $order->addItem($orderItem); } From d3348383263e8e11ee79d35de17bda3c35bfa1a8 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 14 Aug 2019 16:53:56 +0300 Subject: [PATCH 218/841] MC-19105: One item purchased turns into two items on reorder (Multi-shipping) --- .../Model/Checkout/Type/Multishipping.php | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php index 0d6c8517320ce..7105fd4e9d26d 100644 --- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php +++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php @@ -23,6 +23,7 @@ * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Multishipping extends \Magento\Framework\DataObject @@ -526,6 +527,7 @@ protected function _addShippingItem($quoteItemId, $data) $quoteItem->setQty($quoteItem->getMultishippingQty()); try { $address = $this->addressRepository->getById($addressId); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock } catch (\Exception $e) { } if (isset($address)) { @@ -565,6 +567,7 @@ public function updateQuoteCustomerShippingAddress($addressId) } try { $address = $this->addressRepository->getById($addressId); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock } catch (\Exception $e) { // } @@ -592,6 +595,7 @@ public function setQuoteCustomerBillingAddress($addressId) } try { $address = $this->addressRepository->getById($addressId); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock } catch (\Exception $e) { // } @@ -825,7 +829,7 @@ public function createOrders() if ($order->getCanSendNewEmailFlag()) { $this->orderSender->send($order); } - $placedAddressItems = array_merge($placedAddressItems, $this->getQuoteAddressItems($order)); + $placedAddressItems = $this->getPlacedAddressItems($order); } $addressErrors = []; @@ -1090,10 +1094,14 @@ public function getCustomer() */ protected function isAddressIdApplicable($addressId) { - $applicableAddressIds = array_map(function ($address) { - /** @var \Magento\Customer\Api\Data\AddressInterface $address */ - return $address->getId(); - }, $this->getCustomer()->getAddresses()); + $applicableAddressIds = array_map( + function ($address) { + /** @var \Magento\Customer\Api\Data\AddressInterface $address */ + return $address->getId(); + }, + $this->getCustomer()->getAddresses() + ); + return !is_numeric($addressId) || in_array($addressId, $applicableAddressIds); } @@ -1279,4 +1287,20 @@ private function getQuoteAddressItems(OrderInterface $order): array return $placedAddressItems; } + + /** + * Returns placed address items + * + * @param OrderInterface $order + * @return array + */ + private function getPlacedAddressItems(OrderInterface $order): array + { + $placedAddressItems = []; + foreach ($this->getQuoteAddressItems($order) as $key => $quoteAddressItem) { + $placedAddressItems[$key] = $quoteAddressItem; + } + + return $placedAddressItems; + } } From 53257cb1400b91aca4e9602724b782622c5f1769 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Wed, 14 Aug 2019 17:22:36 +0300 Subject: [PATCH 219/841] =?UTF-8?q?MC-18071:=20Reviews=20not=20displaying?= =?UTF-8?q?=20on=20product=20page=20with=20"Product=20=E2=80=93=20Full=20W?= =?UTF-8?q?idth"=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/frontend/templates/product/view/list.phtml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml b/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml index 347686d5c2ba4..ed8ff0595a896 100644 --- a/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml +++ b/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml @@ -11,9 +11,11 @@ $format = $block->getDateFormat() ?: \IntlDateFormatter::SHORT; ?> <?php if (count($_items)) : ?> <div class="block review-list" id="customer-reviews"> - <div class="block-title"> - <strong><?= $block->escapeHtml(__('Customer Reviews')) ?></strong> - </div> + <?php if (!$block->getHideTitle()): ?> + <div class="block-title"> + <strong><?= $block->escapeHtml(__('Customer Reviews')) ?></strong> + </div> + <?php endif ?> <div class="block-content"> <div class="toolbar review-toolbar"> <?= $block->getChildHtml('toolbar') ?> From c09d8c0ae032fe6c0fe324dd81d0ee08f5aa63a0 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Wed, 14 Aug 2019 16:08:07 +0300 Subject: [PATCH 220/841] MAGETWO-44170: Not pass function test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Fix mftf --- .../Mftf/ActionGroup/AdminCategoryActionGroup.xml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 90d732c9654e1..af919e5562729 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -75,7 +75,11 @@ <attachFile selector="{{AdminCategoryContentSection.uploadImageFile}}" userInput="{{image.file}}" stepKey="uploadFile"/> <waitForAjaxLoad time="30" stepKey="waitForAjaxUpload"/> <waitForLoadingMaskToDisappear stepKey="waitForLoading"/> - <see selector="{{AdminCategoryContentSection.imageFileName}}" userInput="{{image.file}}" stepKey="seeImage"/> + <grabTextFrom selector="{{AdminCategoryContentSection.imageFileName}}" stepKey="grabCategoryFileName"/> + <assertRegExp stepKey="assertEquals" message="pass"> + <expectedResult type="string">/magento-logo(_[0-9]+)*?\.png$/</expectedResult> + <actualResult type="variable">grabCategoryFileName</actualResult> + </assertRegExp> </actionGroup> <!-- Remove image from category --> @@ -96,7 +100,11 @@ <conditionalClick selector="{{AdminCategoryContentSection.sectionHeader}}" dependentSelector="{{AdminCategoryContentSection.uploadButton}}" visible="false" stepKey="openContentSection"/> <waitForPageLoad stepKey="waitForPageLoad"/> <waitForElementVisible selector="{{AdminCategoryContentSection.uploadButton}}" stepKey="seeImageSectionIsReady"/> - <see selector="{{AdminCategoryContentSection.imageFileName}}" userInput="{{image.file}}" stepKey="seeImage"/> + <grabTextFrom selector="{{AdminCategoryContentSection.imageFileName}}" stepKey="grabCategoryFileName"/> + <assertRegExp stepKey="assertEquals" message="pass"> + <expectedResult type="string">/magento-logo(_[0-9]+)*?\.png$/</expectedResult> + <actualResult type="variable">grabCategoryFileName</actualResult> + </assertRegExp> </actionGroup> <!-- Action to navigate to Media Gallery. Used in tests to cleanup uploaded images --> From d7606f4cb8ca4769d49123aea69bb61b527a1c1b Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Wed, 14 Aug 2019 22:51:03 +0700 Subject: [PATCH 221/841] Resolve Console error when clicking checkbox at "Newsletter Problems Report" (issue 24102) --- .../view/adminhtml/templates/problem/list.phtml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml b/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml index a3d88de9d35b2..29382930df3e6 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml +++ b/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml @@ -18,9 +18,11 @@ require(["prototype", "mage/adminhtml/events"], function(){ problemController = { checkCheckboxes:function (controlCheckbox) { var elements = $('problemGrid').getElementsByClassName('problemCheckbox'); - elements.each(function (obj) { - obj.checked = controlCheckbox.checked; - }); + if (elements && elements.length) { + elements.each(function (obj) { + obj.checked = controlCheckbox.checked; + }); + } }, rowClick:function (e) { if (!Event.element(e).hasClassName('problemCheckbox')) { From f07847f86b7d335aed937b4160d6156dcb2950b3 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 14 Aug 2019 11:56:18 -0500 Subject: [PATCH 222/841] MC-19102: Adding new search request and use search retrieve results --- .../Model/Resolver/Category/Products.php | 41 ++++++++++++++----- .../Model/Resolver/Products.php | 3 -- .../Model/Resolver/Products/Query/Search.php | 2 + 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php index e0580213ddea7..abc5ae7e1da7f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Category/Products.php @@ -8,6 +8,9 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Category; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogGraphQl\DataProvider\Product\SearchCriteriaBuilder; +use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search; +use Magento\Framework\App\ObjectManager; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -27,27 +30,46 @@ class Products implements ResolverInterface /** * @var Builder + * @deprecated */ private $searchCriteriaBuilder; /** * @var Filter + * @deprecated */ private $filterQuery; + /** + * @var Search + */ + private $searchQuery; + + /** + * @var SearchCriteriaBuilder + */ + private $searchApiCriteriaBuilder; + /** * @param ProductRepositoryInterface $productRepository * @param Builder $searchCriteriaBuilder * @param Filter $filterQuery + * @param Search $searchQuery + * @param SearchCriteriaBuilder $searchApiCriteriaBuilder */ public function __construct( ProductRepositoryInterface $productRepository, Builder $searchCriteriaBuilder, - Filter $filterQuery + Filter $filterQuery, + Search $searchQuery = null, + SearchCriteriaBuilder $searchApiCriteriaBuilder = null ) { $this->productRepository = $productRepository; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->filterQuery = $filterQuery; + $this->searchQuery = $searchQuery ?? ObjectManager::getInstance()->get(Search::class); + $this->searchApiCriteriaBuilder = $searchApiCriteriaBuilder ?? + ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); } /** @@ -60,21 +82,20 @@ public function resolve( array $value = null, array $args = null ) { - $args['filter'] = [ - 'category_id' => [ - 'eq' => $value['id'] - ] - ]; - $searchCriteria = $this->searchCriteriaBuilder->build($field->getName(), $args); if ($args['currentPage'] < 1) { throw new GraphQlInputException(__('currentPage value must be greater than 0.')); } if ($args['pageSize'] < 1) { throw new GraphQlInputException(__('pageSize value must be greater than 0.')); } - $searchCriteria->setCurrentPage($args['currentPage']); - $searchCriteria->setPageSize($args['pageSize']); - $searchResult = $this->filterQuery->getResult($searchCriteria, $info); + + $args['filter'] = [ + 'category_id' => [ + 'eq' => $value['id'] + ] + ]; + $searchCriteria = $this->searchApiCriteriaBuilder->build($args, false); + $searchResult = $this->searchQuery->getResult($searchCriteria, $info); //possible division by 0 if ($searchCriteria->getPageSize()) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 9c8d3266c4d2d..bb94502c41ef1 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -103,9 +103,6 @@ public function resolve( isset($productFields['filters']) ); - $searchCriteria->setCurrentPage($args['currentPage']); - $searchCriteria->setPageSize($args['pageSize']); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index daa3619d17b83..7d55b56f854a2 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -120,6 +120,8 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, true); + $searchCriteria->setPageSize($realPageSize); + $searchCriteria->setCurrentPage($realCurrentPage); //possible division by 0 if ($realPageSize) { $maxPages = (int)ceil($searchResult->getTotalCount() / $realPageSize); From 03a990747b74019321ab6077e4691635358a9a24 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Wed, 14 Aug 2019 13:11:47 -0500 Subject: [PATCH 223/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - updated data fixture --- .../products_with_layered_navigation_custom_attribute.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php index 00678ae904def..d913e3075622e 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -124,12 +124,13 @@ /** @var $productRepository \Magento\Catalog\Api\ProductRepositoryInterface */ $productRepository = $objectManager->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); -$productsWithNewAttributeSet = ['simple', 'simple-4']; +$productsWithNewAttributeSet = ['simple', '12345', 'simple-4']; foreach ($productsWithNewAttributeSet as $sku) { try { $product = $productRepository->get($sku, false, null, true); $product->setAttributeSetId($attributeSet->getId()); + $product->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); $productRepository->save($product); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { From 19cb52281276d6417ee119a9d965daa84454be52 Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk <odubovyk@magento.com> Date: Wed, 14 Aug 2019 16:37:55 -0500 Subject: [PATCH 224/841] MC-19062: Can't upload video form wysiwyg - fixed --- .../view/base/web/tiny_mce/plugins/media/editor_plugin_src.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js index a0d4ef2ae38b8..0da100efcbf50 100644 --- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js +++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js @@ -255,7 +255,7 @@ vspace : data.vspace, src : self.editor.theme.url + '/img/trans.gif', 'class' : 'mceItemMedia mceItem' + self.getType(data.type).name, - 'data-mce-json' : JSON.serialize(data, "'") + 'data-mce-json' : JSON.serialize(data) }); img.width = data.width || (data.type == 'audio' ? "300" : "320"); @@ -880,7 +880,7 @@ vspace : vspace, align : align, bgcolor : bgcolor, - "data-mce-json" : JSON.serialize(data, "'") + "data-mce-json" : JSON.serialize(data) }); } }); From 604c2cf8aafc057ec6ff84b54ad2a2c5a8369ed2 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Wed, 14 Aug 2019 23:10:16 -0500 Subject: [PATCH 225/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProduct.php | 53 ++++++++++++++----- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 55a234bb8ae27..53416f2d3f693 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -8,7 +8,11 @@ use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; -use Magento\Framework\App\ObjectManager; +use Magento\CatalogRule\Model\Rule; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\ScopeInterface; /** * Reindex rule relations with products. @@ -16,7 +20,7 @@ class ReindexRuleProduct { /** - * @var \Magento\Framework\App\ResourceConnection + * @var ResourceConnection */ private $resource; @@ -31,25 +35,40 @@ class ReindexRuleProduct private $tableSwapper; /** - * @param \Magento\Framework\App\ResourceConnection $resource + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var TimezoneInterface + */ + private $localeDate; + + /** + * @param ResourceConnection $resource * @param ActiveTableSwitcher $activeTableSwitcher - * @param TableSwapper|null $tableSwapper + * @param TableSwapper $tableSwapper + * @param ScopeConfigInterface $scopeConfig + * @param TimezoneInterface $localeDate */ public function __construct( - \Magento\Framework\App\ResourceConnection $resource, + ResourceConnection $resource, ActiveTableSwitcher $activeTableSwitcher, - TableSwapper $tableSwapper = null + TableSwapper $tableSwapper, + ScopeConfigInterface $scopeConfig, + TimezoneInterface $localeDate ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; - $this->tableSwapper = $tableSwapper ?? - ObjectManager::getInstance()->get(TableSwapper::class); + $this->tableSwapper = $tableSwapper; + $this->scopeConfig = $scopeConfig; + $this->localeDate = $localeDate; } /** * Reindex information about rule relations with products. * - * @param \Magento\CatalogRule\Model\Rule $rule + * @param Rule $rule * @param int $batchCount * @param bool $useAdditionalTable * @return bool @@ -57,7 +76,7 @@ public function __construct( * @SuppressWarnings(PHPMD.NPathComplexity) */ public function execute( - \Magento\CatalogRule\Model\Rule $rule, + Rule $rule, $batchCount, $useAdditionalTable = false ) { @@ -84,9 +103,6 @@ public function execute( $ruleId = $rule->getId(); $customerGroupIds = $rule->getCustomerGroupIds(); - $fromTime = strtotime($rule->getFromDate()); - $toTime = strtotime($rule->getToDate()); - $toTime = $toTime ? $toTime + \Magento\CatalogRule\Model\Indexer\IndexBuilder::SECONDS_IN_DAY - 1 : 0; $sortOrder = (int)$rule->getSortOrder(); $actionOperator = $rule->getSimpleAction(); $actionAmount = $rule->getDiscountAmount(); @@ -99,6 +115,16 @@ public function execute( if (empty($validationByWebsite[$websiteId])) { continue; } + + $scopeTzPath = $this->localeDate->getDefaultTimezonePath(); + $scopeTz = new \DateTimeZone( + $this->scopeConfig->getValue($scopeTzPath, ScopeInterface::SCOPE_WEBSITE, $websiteId) + ); + $fromTime = (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp(); + $toTime = $rule->getToDate() + ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1 + : 0; + foreach ($customerGroupIds as $customerGroupId) { $rows[] = [ 'rule_id' => $ruleId, @@ -123,6 +149,7 @@ public function execute( if (!empty($rows)) { $connection->insertMultiple($indexTable, $rows); } + return true; } } From 1b7b6f8dfe727058f31ecbc1e2f32fe0d2dee5d1 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Wed, 14 Aug 2019 23:40:33 -0500 Subject: [PATCH 226/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProduct.php | 38 +++++-------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 53416f2d3f693..33df2143e48d2 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -9,10 +9,8 @@ use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; use Magento\CatalogRule\Model\Rule; -use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Store\Model\ScopeInterface; +use Magento\Framework\Stdlib\DateTime\Timezone\LocalizedDateToUtcConverterInterface; /** * Reindex rule relations with products. @@ -35,34 +33,26 @@ class ReindexRuleProduct private $tableSwapper; /** - * @var ScopeConfigInterface + * @var LocalizedDateToUtcConverterInterface */ - private $scopeConfig; - - /** - * @var TimezoneInterface - */ - private $localeDate; + private $dateToUtcConverter; /** * @param ResourceConnection $resource * @param ActiveTableSwitcher $activeTableSwitcher * @param TableSwapper $tableSwapper - * @param ScopeConfigInterface $scopeConfig - * @param TimezoneInterface $localeDate + * @param LocalizedDateToUtcConverterInterface $dateToUtcConverter */ public function __construct( ResourceConnection $resource, ActiveTableSwitcher $activeTableSwitcher, TableSwapper $tableSwapper, - ScopeConfigInterface $scopeConfig, - TimezoneInterface $localeDate + LocalizedDateToUtcConverterInterface $dateToUtcConverter ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; $this->tableSwapper = $tableSwapper; - $this->scopeConfig = $scopeConfig; - $this->localeDate = $localeDate; + $this->dateToUtcConverter = $dateToUtcConverter; } /** @@ -75,11 +65,8 @@ public function __construct( * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function execute( - Rule $rule, - $batchCount, - $useAdditionalTable = false - ) { + public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) + { if (!$rule->getIsActive() || empty($rule->getWebsiteIds())) { return false; } @@ -109,20 +96,15 @@ public function execute( $actionStop = $rule->getStopRulesProcessing(); $rows = []; - foreach ($productIds as $productId => $validationByWebsite) { foreach ($websiteIds as $websiteId) { if (empty($validationByWebsite[$websiteId])) { continue; } - $scopeTzPath = $this->localeDate->getDefaultTimezonePath(); - $scopeTz = new \DateTimeZone( - $this->scopeConfig->getValue($scopeTzPath, ScopeInterface::SCOPE_WEBSITE, $websiteId) - ); - $fromTime = (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp(); + $fromTime = strtotime($this->dateToUtcConverter->convertLocalizedDateToUtc($rule->getFromDate())); $toTime = $rule->getToDate() - ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1 + ? strtotime($this->dateToUtcConverter->convertLocalizedDateToUtc($rule->getToDate())) : 0; foreach ($customerGroupIds as $customerGroupId) { From e0a82d0c88458a0274ba6cd27ad0d0122a1b2023 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 15 Aug 2019 14:46:19 +0300 Subject: [PATCH 227/841] MC-18561: After changing store view the cms page is not redirecting correctly --- .../CmsUrlRewrite/Plugin/Cms/Model/Store/View.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php index c4b9655bbc7d6..e56225cbe2548 100644 --- a/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php +++ b/app/code/Magento/CmsUrlRewrite/Plugin/Cms/Model/Store/View.php @@ -65,21 +65,18 @@ public function __construct( * Replace cms page url rewrites on store view save * * @param ResourceStore $object - * @param \Closure $proceed - * @param AbstractModel $store - * @return mixed + * @param ResourceStore $result + * @param ResourceStore $store + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundSave(ResourceStore $object, \Closure $proceed, AbstractModel $store) + public function afterSave(ResourceStore $object, ResourceStore $result, AbstractModel $store): void { - $result = $proceed($store); if ($store->isObjectNew() || $store->dataHasChangedFor('group_id')) { $this->urlPersist->replace( $this->generateCmsPagesUrls((int)$store->getId()) ); } - - return $result; } /** From 565d0b10ec4719a3e9d5befa05898a71329e86ea Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Thu, 15 Aug 2019 10:09:04 -0500 Subject: [PATCH 228/841] Removed unused const --- .../AssignCouponDataAfterOrderCustomerAssignObserver.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php index 4dadfb453f51e..2d771e4560fcf 100644 --- a/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php +++ b/app/code/Magento/SalesRule/Observer/AssignCouponDataAfterOrderCustomerAssignObserver.php @@ -19,9 +19,7 @@ */ class AssignCouponDataAfterOrderCustomerAssignObserver implements ObserverInterface { - const EVENT_KEY_CUSTOMER = 'customer'; - - const EVENT_KEY_ORDER = 'order'; + private const EVENT_KEY_ORDER = 'order'; /** * @var UpdateCouponUsages From b19440d0539e2dbe95ba92b8ffd743f2893997cb Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Thu, 15 Aug 2019 10:20:19 -0500 Subject: [PATCH 229/841] MC-19090: Category Image from Gallery is not saved --- .../Catalog/Model/Category/Attribute/Backend/Image.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index 073b1fa44a07e..07fd1a50edc97 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -125,7 +125,7 @@ public function beforeSave($object) } if ($imageName = $this->getUploadedImageName($value)) { - if (!$this->fileResidesOutsideCategoryDir($object->getData($attributeName))) { + if (!$this->fileResidesOutsideCategoryDir($value)) { $imageName = $this->checkUniqueImageName($imageName); } $object->setData($this->additionalData . $attributeName, $value); @@ -178,13 +178,15 @@ private function fileResidesOutsideCategoryDir($value) } $fileUrl = ltrim($value[0]['url'], '/'); - $baseMediaDir = $this->_filesystem->getUri(DirectoryList::MEDIA); + $imageUploader = $this->getImageUploader(); + $baseMediaDir = $this->_filesystem->getUri(DirectoryList::MEDIA) + . DIRECTORY_SEPARATOR . $imageUploader->getBasePath(); if (!$baseMediaDir) { return false; } - return strpos($fileUrl, $baseMediaDir) === 0; + return strpos($fileUrl, $baseMediaDir) === false; } /** From ae7016a8ef7221bc8512791b1e678f442ccf82ab Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 15 Aug 2019 11:38:46 -0500 Subject: [PATCH 230/841] MC-19102: Adding new search request and use search retrieve results --- .../Model/Resolver/Products.php | 2 +- .../Model/Resolver/Products/Query/Search.php | 11 ++++-- .../Plugin/Search/Request/ConfigReader.php | 35 ++++++++++++------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index bb94502c41ef1..6a17f730c0f1a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -103,7 +103,7 @@ public function resolve( isset($productFields['filters']) ); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); + $searchResult = $this->searchQuery->getResult($searchCriteria, $info, $args); if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) { throw new GraphQlInputException( diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index 7d55b56f854a2..c7dd2a8b05b79 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -87,15 +87,20 @@ public function __construct( * * @param SearchCriteriaInterface $searchCriteria * @param ResolveInfo $info + * @param array $args * @return SearchResult * @throws \Exception */ - public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $info) : SearchResult - { + public function getResult( + SearchCriteriaInterface $searchCriteria, + ResolveInfo $info, + array $args = [] + ): SearchResult { $idField = $this->metadataPool->getMetadata( \Magento\Catalog\Api\Data\ProductInterface::class )->getIdentifierField(); + $isSearch = isset($args['search']); $realPageSize = $searchCriteria->getPageSize(); $realCurrentPage = $searchCriteria->getCurrentPage(); // Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround @@ -118,7 +123,7 @@ public function getResult(SearchCriteriaInterface $searchCriteria, ResolveInfo $ $searchCriteriaIds->setPageSize($realPageSize); $searchCriteriaIds->setCurrentPage($realCurrentPage); - $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, true); + $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, $isSearch); $searchCriteria->setPageSize($realPageSize); $searchCriteria->setCurrentPage($realCurrentPage); diff --git a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php index 70e312ff4e2ee..151be89177744 100644 --- a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php +++ b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php @@ -6,11 +6,11 @@ namespace Magento\CatalogGraphQl\Plugin\Search\Request; use Magento\Catalog\Api\Data\EavAttributeInterface; -use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\DataProvider; use Magento\CatalogSearch\Model\Search\RequestGenerator; use Magento\CatalogSearch\Model\Search\RequestGenerator\GeneratorResolver; use Magento\Framework\Search\Request\FilterInterface; use Magento\Framework\Search\Request\QueryInterface; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; /** * Add search request configuration to config for give ability filter and search products during GraphQL request @@ -33,26 +33,28 @@ class ConfigReader private $requestName = 'graphql_product_search'; /** - * @var DataProvider + * @var GeneratorResolver */ - private $dataProvider; + private $generatorResolver; /** - * @var GeneratorResolver + * @var CollectionFactory */ - private $generatorResolver; + private $productAttributeCollectionFactory; /** Bucket name suffix */ private const BUCKET_SUFFIX = '_bucket'; /** - * @param DataProvider $dataProvider * @param GeneratorResolver $generatorResolver + * @param CollectionFactory $productAttributeCollectionFactory */ - public function __construct(DataProvider $dataProvider, GeneratorResolver $generatorResolver) - { - $this->dataProvider = $dataProvider; + public function __construct( + GeneratorResolver $generatorResolver, + CollectionFactory $productAttributeCollectionFactory + ) { $this->generatorResolver = $generatorResolver; + $this->productAttributeCollectionFactory = $productAttributeCollectionFactory; } /** @@ -89,9 +91,18 @@ public function afterRead( private function getSearchableAttributes(): array { $attributes = []; - foreach ($this->dataProvider->getSearchableAttributes() as $attribute) { - $attributes[$attribute->getAttributeCode()] = $attribute; + /** @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection $productAttributes */ + $productAttributes = $this->productAttributeCollectionFactory->create(); + $productAttributes->addFieldToFilter( + ['is_searchable', 'is_visible_in_advanced_search', 'is_filterable', 'is_filterable_in_search'], + [1, 1, [1, 2], 1] + ); + + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ + foreach ($productAttributes->getItems() as $attribute) { + $attributes[$attribute->getAttributeCode()] = $attribute; } + return $attributes; } @@ -106,7 +117,7 @@ private function generateRequest() $request = []; foreach ($this->getSearchableAttributes() as $attribute) { if (\in_array($attribute->getAttributeCode(), ['price', 'visibility', 'category_ids'])) { - //same fields have special semantics + //some fields have special semantics continue; } $queryName = $attribute->getAttributeCode() . '_query'; From 3c6eec41102cdccb609d0345b01563160e9ad931 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Thu, 15 Aug 2019 12:17:28 -0500 Subject: [PATCH 231/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - added use cases --- .../GraphQl/Catalog/ProductSearchTest.php | 267 ++++++++++++++++++ ...th_layered_navigation_custom_attribute.php | 8 +- 2 files changed, 272 insertions(+), 3 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 4ce8ad8dab393..e80387394ffaf 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -18,6 +18,7 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Catalog\Model\Product; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\Framework\DataObject; /** @@ -89,6 +90,272 @@ public function testFilterLn() ); } + /** + * Advanced Search which uses product attribute to filter out the results + * + * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testAdvancedSearchByOneCustomAttribute() + { + $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); + + $query = <<<QUERY +{ + products(filter:{ + second_test_configurable: {eq: "{$optionValue}"} + }, + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + + } + + } +} +QUERY; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product1 = $productRepository->get('simple'); + $product2 = $productRepository->get('12345'); + $product3 = $productRepository->get('simple-4'); + $filteredProducts = [$product1, $product2, $product3 ]; + $response = $this->graphQlQuery($query); + $this->assertEquals(3, $response['products']['total_count']); + $this->assertTrue(count($response['products']['filters']) > 0, 'Product filters is not empty'); + $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); + // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall + for ($itemIndex = 0; $itemIndex < count($filteredProducts); $itemIndex++) { + $this->assertNotEmpty($productItemsInResponse[$itemIndex]); + //validate that correct products are returned + $this->assertResponseFields( + $productItemsInResponse[$itemIndex][0], + [ 'name' => $filteredProducts[$itemIndex]->getName(), + 'sku' => $filteredProducts[$itemIndex]->getSku() + ] + ); + } + + /** @var \Magento\Eav\Model\Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $attribute = $eavConfig->getAttribute('catalog_product', 'second_test_configurable'); + + // Validate custom attribute filter layer data + $this->assertResponseFields( + $response['products']['filters'][2], + [ + 'name' => $attribute->getDefaultFrontendLabel(), + 'request_var'=> $attribute->getAttributeCode(), + 'filter_items_count'=> 1, + 'filter_items' => [ + [ + 'label' => 'Option 3', + 'items_count' => 3, + 'value_string' => $optionValue, + '__typename' =>'LayerFilterItem' + ], + ], + ] + ); + } + + /** + * Get the option value for the custom attribute to be used in the graphql query + * + * @param string $attributeCode + * @return string + */ + private function getDefaultAttributeOptionValue(string $attributeCode) : string + { + /** @var \Magento\Eav\Model\Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); + /** @var AttributeOptionInterface[] $options */ + $options = $attribute->getOptions(); + $defaultOptionValue = $options[1]->getValue(); + return $defaultOptionValue; + } + + /** + * Full text search for Product and then filter the results by custom attribute + * + * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testFullTextSearchForProductAndFilterByCustomAttribute() + { + $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); + + $query = <<<QUERY +{ + products(search:"Simple", + filter:{ + second_test_configurable: {eq: "{$optionValue}"} + }, + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + + } + + } + +} +QUERY; + $response = $this->graphQlQuery($query); + //Verify total count of the products returned + $this->assertEquals(3, $response['products']['total_count']); + $expectedFilterLayers = + [ + ['name' => 'Price', + 'request_var'=> 'price' + ], + ['name' => 'Category', + 'request_var'=> 'category_id' + ], + ['name' => 'Second Test Configurable', + 'request_var'=> 'second_test_configurable' + ], + ]; + $layers = array_map(null, $expectedFilterLayers, $response['products']['filters']); + + //Verify all the three layers : Price, Category and Custom attribute layers are created + foreach ($layers as $layerIndex => $layerFilterData) { + $this->assertNotEmpty($layerFilterData); + $this->assertEquals( + $layers[$layerIndex][0]['name'], + $response['products']['filters'][$layerIndex]['name'], + 'Layer name does not match' + ); + $this->assertEquals( + $layers[$layerIndex][0]['request_var'], + $response['products']['filters'][$layerIndex]['request_var'], + 'request_var does not match' + ) ; + } + + // Validate the price filter layer data from the response + $this->assertResponseFields( + $response['products']['filters'][0], + [ + 'name' => 'Price', + 'request_var'=> 'price', + 'filter_items_count'=> 2, + 'filter_items' => [ + [ + 'label' => '10-20', + 'items_count' => 2, + 'value_string' => '10_20', + '__typename' =>'LayerFilterItem' + ], + [ + 'label' => '40-*', + 'items_count' => 1, + 'value_string' => '40_*', + '__typename' =>'LayerFilterItem' + ], + ], + ] + ); + } + + /** + * Filter by mu;ltiple attributes like category_id and custom attribute + * + * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testFilterByCategoryIdAndCustomAttribute() + { + $categoryId = 13; + $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); + $query = <<<QUERY +{ + products(filter:{ + category_id : {eq:"{$categoryId}"} + second_test_configurable: {eq: "{$optionValue}"} + }, + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + + } + + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(2, $response['products']['total_count']); + } + + + /** * Get array with expected data for layered navigation filters * diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php index d913e3075622e..9167bbc1db092 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -68,7 +68,6 @@ /* Assign attribute to attribute set */ $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); - CacheCleaner::cleanAll(); } // create a second attribute if (!$attribute1->getId()) { @@ -113,8 +112,11 @@ $attributeRepository->save($attribute1); /* Assign attribute to attribute set */ - $installer->addAttributeToGroup('catalog_product', $attributeSet->getId(), $attributeSet->getDefaultGroupId(), $attribute1->getId()); - CacheCleaner::cleanAll(); + $installer->addAttributeToGroup('catalog_product', + $attributeSet->getId(), + $attributeSet->getDefaultGroupId(), + $attribute1->getId() + ); } $eavConfig->clear(); From 4e960c31bf345f59a4eccc16832a3a737d4ce8b8 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Thu, 15 Aug 2019 14:13:51 -0500 Subject: [PATCH 232/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../MessageQueue/Setup/ConfigOptionsList.php | 103 ++++++++++++++++++ .../MessageQueue/CallbackInvoker.php | 29 ++++- 2 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php diff --git a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php new file mode 100644 index 0000000000000..bd81ae633429d --- /dev/null +++ b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MessageQueue\Setup; + +use Magento\Framework\Setup\ConfigOptionsListInterface; +use Magento\Framework\Setup\Option\SelectConfigOption; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\Config\Data\ConfigData; +use Magento\Framework\Config\File\ConfigFilePool; + +/** + * Deployment configuration consumers options needed for Setup application + */ +class ConfigOptionsList implements ConfigOptionsListInterface +{ + /** + * Input key for the option + */ + const INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES ='consumers-wait-for-messages'; + + /** + * Path to the value in the deployment config + */ + const CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES = 'queue/consumers_wait_for_messages'; + + /** + * Default value + */ + const DEFULT_CONSUMERS_WAIT_FOR_MESSAGES = 1; + + private $selectOptions = [0, 1]; + + /** + * @inheritdoc + */ + public function getOptions() + { + return [ + new SelectConfigOption( + self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES, + SelectConfigOption::FRONTEND_WIZARD_SELECT, + $this->selectOptions, + self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES, + 'Should consumers wait for message from the queue? 1 - Yes, 0 - No', + self::DEFULT_CONSUMERS_WAIT_FOR_MESSAGES + ), + ]; + } + + /** + * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function createConfig(array $data, DeploymentConfig $deploymentConfig) + { + $configData = new ConfigData(ConfigFilePool::APP_ENV); + + if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES)) { + $configData->set( + self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES, + (int)$data[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES] + ); + } + + return[$configData]; + } + + /** + * @inheritdoc + */ + public function validate(array $options, DeploymentConfig $deploymentConfig) + { + $errors = []; + + if (!$this->isDataEmpty($options, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES) + && !in_array($options[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES], $this->selectOptions)) { + $errors[] = 'You can use only 1 or 0 for ' . self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES . ' option'; + } + + return $errors; + } + + /** + * Check if data ($data) with key ($key) is empty + * + * @param array $data + * @param string $key + * @return bool + */ + private function isDataEmpty(array $data, $key) + { + if (isset($data[$key]) && $data[$key] !== '') { + return false; + } + + return true; + } +} diff --git a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php index fe0a84af3ca93..559959b55fc61 100644 --- a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php +++ b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php @@ -8,6 +8,7 @@ use Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompareInterface; use Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface; +use Magento\Framework\App\DeploymentConfig; /** * Class CallbackInvoker to invoke callbacks for consumer classes @@ -29,16 +30,24 @@ class CallbackInvoker implements CallbackInvokerInterface */ private $poisonPillCompare; + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + /** * @param PoisonPillReadInterface $poisonPillRead * @param PoisonPillCompareInterface $poisonPillCompare + * @param DeploymentConfig $deploymentConfig */ public function __construct( PoisonPillReadInterface $poisonPillRead, - PoisonPillCompareInterface $poisonPillCompare + PoisonPillCompareInterface $poisonPillCompare, + DeploymentConfig $deploymentConfig ) { $this->poisonPillRead = $poisonPillRead; $this->poisonPillCompare = $poisonPillCompare; + $this->deploymentConfig = $deploymentConfig; } /** @@ -56,13 +65,29 @@ public function invoke(QueueInterface $queue, $maxNumberOfMessages, $callback) do { $message = $queue->dequeue(); // phpcs:ignore Magento2.Functions.DiscouragedFunction - } while ($message === null && (sleep(1) === 0)); + } while ($message === null && $this->isWaitingNextMessage() && (sleep(1) === 0)); + + if ($message === null) { + break; + } + if (false === $this->poisonPillCompare->isLatestVersion($this->poisonPillVersion)) { $queue->reject($message); // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage exit(0); } + $callback($message); } } + + /** + * Checks if consumers should wait for message from the queue + * + * @return bool + */ + private function isWaitingNextMessage(): bool + { + return $this->deploymentConfig->get('queue/consumers_wait_for_messages', 1) === 1; + } } From 8758911b7d09c3a269977ba75a275d6810c657e7 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Thu, 15 Aug 2019 15:06:26 -0500 Subject: [PATCH 233/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../Framework/MessageQueue/Test/Unit/ConsumerTest.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php index 7a3eb3b16baca..e7ee0e19a1d43 100644 --- a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php +++ b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php @@ -77,6 +77,11 @@ class ConsumerTest extends \PHPUnit\Framework\TestCase */ private $poisonPillCompare; + /** + * @var \Magento\Framework\App\DeploymentConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $deploymentConfig; + /** * Set up. * @@ -95,6 +100,7 @@ protected function setUp() ->disableOriginalConstructor()->getMock(); $this->logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) ->disableOriginalConstructor()->getMock(); + $this->deploymentConfig = $this->createMock(\Magento\Framework\App\DeploymentConfig::class); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->poisonPillCompare = $this->getMockBuilder(PoisonPillCompareInterface::class) @@ -104,7 +110,8 @@ protected function setUp() //Hard dependency used because CallbackInvoker invokes closure logic defined inside of Customer class. $this->callbackInvoker = new \Magento\Framework\MessageQueue\CallbackInvoker( $this->poisonPillRead, - $this->poisonPillCompare + $this->poisonPillCompare, + $this->deploymentConfig ); $this->consumer = $objectManager->getObject( \Magento\Framework\MessageQueue\Consumer::class, From 8e199c658305858682d4777a76afa8fd171c75e0 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 15 Aug 2019 15:37:51 -0500 Subject: [PATCH 234/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProduct.php | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 33df2143e48d2..e589c8595ce2c 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -10,7 +10,8 @@ use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; use Magento\CatalogRule\Model\Rule; use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Stdlib\DateTime\Timezone\LocalizedDateToUtcConverterInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\ScopeInterface; /** * Reindex rule relations with products. @@ -33,26 +34,26 @@ class ReindexRuleProduct private $tableSwapper; /** - * @var LocalizedDateToUtcConverterInterface + * @var TimezoneInterface */ - private $dateToUtcConverter; + private $localeDate; /** * @param ResourceConnection $resource * @param ActiveTableSwitcher $activeTableSwitcher * @param TableSwapper $tableSwapper - * @param LocalizedDateToUtcConverterInterface $dateToUtcConverter + * @param TimezoneInterface $localeDate */ public function __construct( ResourceConnection $resource, ActiveTableSwitcher $activeTableSwitcher, TableSwapper $tableSwapper, - LocalizedDateToUtcConverterInterface $dateToUtcConverter + TimezoneInterface $localeDate ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; $this->tableSwapper = $tableSwapper; - $this->dateToUtcConverter = $dateToUtcConverter; + $this->localeDate = $localeDate; } /** @@ -96,17 +97,20 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) $actionStop = $rule->getStopRulesProcessing(); $rows = []; - foreach ($productIds as $productId => $validationByWebsite) { - foreach ($websiteIds as $websiteId) { + foreach ($websiteIds as $websiteId) { + $scopeTz = new \DateTimeZone( + $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId) + ); + $fromTime = (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp(); + $toTime = $rule->getToDate() + ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1 + : 0; + + foreach ($productIds as $productId => $validationByWebsite) { if (empty($validationByWebsite[$websiteId])) { continue; } - $fromTime = strtotime($this->dateToUtcConverter->convertLocalizedDateToUtc($rule->getFromDate())); - $toTime = $rule->getToDate() - ? strtotime($this->dateToUtcConverter->convertLocalizedDateToUtc($rule->getToDate())) - : 0; - foreach ($customerGroupIds as $customerGroupId) { $rows[] = [ 'rule_id' => $ruleId, From 35a868adf054a39160b53fe3abb663b6b0a452bb Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Thu, 15 Aug 2019 16:44:12 -0500 Subject: [PATCH 235/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - added use cases --- .../GraphQl/Catalog/ProductSearchTest.php | 45 ++++++++++++++++++- ...th_layered_navigation_custom_attribute.php | 14 +++--- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index e80387394ffaf..e607e2d608786 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -258,7 +258,7 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() 'request_var'=> 'category_id' ], ['name' => 'Second Test Configurable', - 'request_var'=> 'second_test_configurable' + 'request_var'=> 'second_test_configurable' ], ]; $layers = array_map(null, $expectedFilterLayers, $response['products']['filters']); @@ -352,6 +352,49 @@ public function testFilterByCategoryIdAndCustomAttribute() QUERY; $response = $this->graphQlQuery($query); $this->assertEquals(2, $response['products']['total_count']); + $actualCategoryFilterItems = $response['products']['filters'][1]['filter_items']; + //Validate the number of categories/sub-categories that contain the products with the custom attribute + $this->assertCount(6,$actualCategoryFilterItems); + + $expectedCategoryFilterItems = + [ + [ 'label' => 'Category 1', + 'items_count'=> 2 + ], + [ 'label' => 'Category 1.1', + 'items_count'=> 1 + ], + [ 'label' => 'Movable Position 2', + 'items_count'=> 1 + ], + [ 'label' => 'Movable Position 3', + 'items_count'=> 1 + ], + [ 'label' => 'Category 12', + 'items_count'=> 1 + ], + [ 'label' => 'Category 1.2', + 'items_count'=> 2 + ], + ]; + $categoryFilterItems = array_map(null, $expectedCategoryFilterItems,$actualCategoryFilterItems); + +//Validate the categories and sub-categories data in the filter layer + foreach ($categoryFilterItems as $index => $categoryFilterData) { + $this->assertNotEmpty($categoryFilterData); + $this->assertEquals( + $categoryFilterItems[$index][0]['label'], + $actualCategoryFilterItems[$index]['label'], + 'Category is incorrect' + ); + $this->assertEquals( + $categoryFilterItems[$index][0]['items_count'], + $actualCategoryFilterItems[$index]['items_count'], + 'Products count in the category is incorrect' + ) ; + } + + } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php index 9167bbc1db092..6cd2819c2d296 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -12,7 +12,6 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\Eav\Api\AttributeRepositoryInterface; -use Magento\TestFramework\Helper\CacheCleaner; $eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); $attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); @@ -112,11 +111,11 @@ $attributeRepository->save($attribute1); /* Assign attribute to attribute set */ - $installer->addAttributeToGroup('catalog_product', + $installer->addAttributeToGroup( + 'catalog_product', $attributeSet->getId(), $attributeSet->getDefaultGroupId(), - $attribute1->getId() - ); + $attribute1->getId()); } $eavConfig->clear(); @@ -132,7 +131,12 @@ try { $product = $productRepository->get($sku, false, null, true); $product->setAttributeSetId($attributeSet->getId()); - $product->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + $product->setStockData( + ['use_config_manage_stock' => 1, + 'qty' => 50, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1] + ); $productRepository->save($product); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { From c7293b05ec6224d2525c9c9d55e8d2138a53cbcf Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Thu, 15 Aug 2019 17:48:36 -0500 Subject: [PATCH 236/841] MC-19090: Category Image from Gallery is not saved --- .../Catalog/Model/Category/Attribute/Backend/Image.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index 07fd1a50edc97..32b5d066193ca 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -178,15 +178,13 @@ private function fileResidesOutsideCategoryDir($value) } $fileUrl = ltrim($value[0]['url'], '/'); - $imageUploader = $this->getImageUploader(); - $baseMediaDir = $this->_filesystem->getUri(DirectoryList::MEDIA) - . DIRECTORY_SEPARATOR . $imageUploader->getBasePath(); + $baseMediaDir = $this->_filesystem->getUri(DirectoryList::MEDIA); if (!$baseMediaDir) { return false; } - return strpos($fileUrl, $baseMediaDir) === false; + return strpos($fileUrl, $baseMediaDir) !== false; } /** From 60c78c1489ed1fb92000af44ed19a02e20f28203 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Thu, 15 Aug 2019 17:50:07 -0500 Subject: [PATCH 237/841] MC-19090: Category Image from Gallery is not saved --- .../Catalog/Model/Category/FileInfo.php | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php index d77f472c6be90..0973db756df87 100644 --- a/app/code/Magento/Catalog/Model/Category/FileInfo.php +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -10,6 +10,8 @@ use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; /** * Class FileInfo @@ -48,16 +50,26 @@ class FileInfo */ private $pubDirectory; + /** + * Store manager + * + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + /** * @param Filesystem $filesystem * @param Mime $mime + * @param StoreManagerInterface $storeManager */ public function __construct( Filesystem $filesystem, - Mime $mime + Mime $mime, + StoreManagerInterface $storeManager ) { $this->filesystem = $filesystem; $this->mime = $mime; + $this->storeManager = $storeManager; } /** @@ -152,7 +164,8 @@ public function isExist($fileName) */ private function getFilePath($fileName) { - $filePath = ltrim($fileName, '/'); + $filePath = $this->removeStorePath($fileName); + $filePath = ltrim($filePath, '/'); $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath); $isFileNameBeginsWithMediaDirectoryPath = $this->isBeginsWithMediaDirectoryPath($fileName); @@ -177,7 +190,8 @@ private function getFilePath($fileName) */ public function isBeginsWithMediaDirectoryPath($fileName) { - $filePath = ltrim($fileName, '/'); + $filePath = $this->removeStorePath($fileName); + $filePath = ltrim($filePath, '/'); $mediaDirectoryRelativeSubpath = $this->getMediaDirectoryPathRelativeToBaseDirectoryPath($filePath); $isFileNameBeginsWithMediaDirectoryPath = strpos($filePath, (string) $mediaDirectoryRelativeSubpath) === 0; @@ -185,6 +199,27 @@ public function isBeginsWithMediaDirectoryPath($fileName) return $isFileNameBeginsWithMediaDirectoryPath; } + /** + * Clean store path in case if it's exists + * + * @param string $path + * @return string + */ + private function removeStorePath(string $path): string + { + $result = $path; + try { + $storeUrl = $this->storeManager->getStore()->getUrl(); + } catch (NoSuchEntityException $e) { + return $result; + } + $path = parse_url($path, PHP_URL_PATH); + $storePath = parse_url($storeUrl, PHP_URL_PATH); + $result = ltrim($path, $storePath); + + return $result; + } + /** * Get media directory subpath relative to base directory path * From 40a433bf1211299ece193fdbe0b580d91b4b9d1f Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 15 Aug 2019 18:32:23 -0500 Subject: [PATCH 238/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProduct.php | 51 +++++------- .../Model/Indexer/ReindexRuleProductPrice.php | 82 +++++++++++-------- .../Indexer/ReindexRuleProductPriceTest.php | 58 ++++++++----- 3 files changed, 107 insertions(+), 84 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index e589c8595ce2c..55a234bb8ae27 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -8,10 +8,7 @@ use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; -use Magento\CatalogRule\Model\Rule; -use Magento\Framework\App\ResourceConnection; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\ObjectManager; /** * Reindex rule relations with products. @@ -19,7 +16,7 @@ class ReindexRuleProduct { /** - * @var ResourceConnection + * @var \Magento\Framework\App\ResourceConnection */ private $resource; @@ -34,40 +31,36 @@ class ReindexRuleProduct private $tableSwapper; /** - * @var TimezoneInterface - */ - private $localeDate; - - /** - * @param ResourceConnection $resource + * @param \Magento\Framework\App\ResourceConnection $resource * @param ActiveTableSwitcher $activeTableSwitcher - * @param TableSwapper $tableSwapper - * @param TimezoneInterface $localeDate + * @param TableSwapper|null $tableSwapper */ public function __construct( - ResourceConnection $resource, + \Magento\Framework\App\ResourceConnection $resource, ActiveTableSwitcher $activeTableSwitcher, - TableSwapper $tableSwapper, - TimezoneInterface $localeDate + TableSwapper $tableSwapper = null ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; - $this->tableSwapper = $tableSwapper; - $this->localeDate = $localeDate; + $this->tableSwapper = $tableSwapper ?? + ObjectManager::getInstance()->get(TableSwapper::class); } /** * Reindex information about rule relations with products. * - * @param Rule $rule + * @param \Magento\CatalogRule\Model\Rule $rule * @param int $batchCount * @param bool $useAdditionalTable * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) - { + public function execute( + \Magento\CatalogRule\Model\Rule $rule, + $batchCount, + $useAdditionalTable = false + ) { if (!$rule->getIsActive() || empty($rule->getWebsiteIds())) { return false; } @@ -91,26 +84,21 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) $ruleId = $rule->getId(); $customerGroupIds = $rule->getCustomerGroupIds(); + $fromTime = strtotime($rule->getFromDate()); + $toTime = strtotime($rule->getToDate()); + $toTime = $toTime ? $toTime + \Magento\CatalogRule\Model\Indexer\IndexBuilder::SECONDS_IN_DAY - 1 : 0; $sortOrder = (int)$rule->getSortOrder(); $actionOperator = $rule->getSimpleAction(); $actionAmount = $rule->getDiscountAmount(); $actionStop = $rule->getStopRulesProcessing(); $rows = []; - foreach ($websiteIds as $websiteId) { - $scopeTz = new \DateTimeZone( - $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId) - ); - $fromTime = (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp(); - $toTime = $rule->getToDate() - ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1 - : 0; - foreach ($productIds as $productId => $validationByWebsite) { + foreach ($productIds as $productId => $validationByWebsite) { + foreach ($websiteIds as $websiteId) { if (empty($validationByWebsite[$websiteId])) { continue; } - foreach ($customerGroupIds as $customerGroupId) { $rows[] = [ 'rule_id' => $ruleId, @@ -135,7 +123,6 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) if (!empty($rows)) { $connection->insertMultiple($indexTable, $rows); } - return true; } } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php index 3d21b50a763eb..f8eae4ed3a71b 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php @@ -6,72 +6,82 @@ namespace Magento\CatalogRule\Model\Indexer; -use Magento\Catalog\Model\Product; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Store\Model\StoreManagerInterface; - /** * Reindex product prices according rule settings. */ class ReindexRuleProductPrice { /** - * @var StoreManagerInterface + * @var \Magento\Store\Model\StoreManagerInterface */ private $storeManager; /** - * @var RuleProductsSelectBuilder + * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder */ private $ruleProductsSelectBuilder; /** - * @var ProductPriceCalculator + * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator */ private $productPriceCalculator; /** - * @var TimezoneInterface + * @var \Magento\Framework\Stdlib\DateTime\DateTime */ - private $localeDate; + private $dateTime; /** - * @var RuleProductPricesPersistor + * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor */ private $pricesPersistor; /** - * @param StoreManagerInterface $storeManager + * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + */ + private $localeDate; + + /** + * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param RuleProductsSelectBuilder $ruleProductsSelectBuilder * @param ProductPriceCalculator $productPriceCalculator - * @param TimezoneInterface $localeDate - * @param RuleProductPricesPersistor $pricesPersistor + * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime + * @param \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate */ public function __construct( - StoreManagerInterface $storeManager, - RuleProductsSelectBuilder $ruleProductsSelectBuilder, - ProductPriceCalculator $productPriceCalculator, - TimezoneInterface $localeDate, - RuleProductPricesPersistor $pricesPersistor + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder $ruleProductsSelectBuilder, + \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator $productPriceCalculator, + \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, + \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate ) { $this->storeManager = $storeManager; $this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder; $this->productPriceCalculator = $productPriceCalculator; - $this->localeDate = $localeDate; + $this->dateTime = $dateTime; $this->pricesPersistor = $pricesPersistor; + $this->localeDate = $localeDate; } /** * Reindex product prices. * * @param int $batchCount - * @param Product|null $product + * @param \Magento\Catalog\Model\Product|null $product * @param bool $useAdditionalTable * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function execute($batchCount, Product $product = null, $useAdditionalTable = false) - { + public function execute( + $batchCount, + \Magento\Catalog\Model\Product $product = null, + $useAdditionalTable = false + ) { + $fromDate = mktime(0, 0, 0, date('m'), date('d') - 1); + $toDate = mktime(0, 0, 0, date('m'), date('d') + 1); + /** * Update products rules prices per each website separately * because for each website date in website's timezone should be used @@ -81,13 +91,8 @@ public function execute($batchCount, Product $product = null, $useAdditionalTabl $dayPrices = []; $stopFlags = []; $prevKey = null; - $storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId()); - $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true); - $previousDate = (clone $currentDate)->modify('-1 day'); - $previousDate->setTime(23, 59, 59); - $nextDate = (clone $currentDate)->modify('+1 day'); - $nextDate->setTime(0, 0, 0); + $storeId = $storeGroup->getDefaultStoreId(); while ($ruleData = $productsStmt->fetch()) { $ruleProductId = $ruleData['product_id']; @@ -105,24 +110,24 @@ public function execute($batchCount, Product $product = null, $useAdditionalTabl } } + $ruleData['from_time'] = $this->roundTime($ruleData['from_time']); + $ruleData['to_time'] = $this->roundTime($ruleData['to_time']); /** * Build prices for each day */ - foreach ([$previousDate, $currentDate, $nextDate] as $date) { - $time = $date->getTimestamp(); + for ($time = $fromDate; $time <= $toDate; $time += IndexBuilder::SECONDS_IN_DAY) { if (($ruleData['from_time'] == 0 || $time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 || $time <= $ruleData['to_time']) ) { $priceKey = $time . '_' . $productKey; - if (isset($stopFlags[$priceKey])) { continue; } if (!isset($dayPrices[$priceKey])) { $dayPrices[$priceKey] = [ - 'rule_date' => $date, + 'rule_date' => $this->localeDate->scopeDate($storeId, $time), 'website_id' => $ruleData['website_id'], 'customer_group_id' => $ruleData['customer_group_id'], 'product_id' => $ruleProductId, @@ -155,6 +160,19 @@ public function execute($batchCount, Product $product = null, $useAdditionalTabl } $this->pricesPersistor->execute($dayPrices, $useAdditionalTable); } + return true; } + + /** + * @param int $timeStamp + * @return int + */ + private function roundTime($timeStamp) + { + if (is_numeric($timeStamp) && $timeStamp != 0) { + $timeStamp = $this->dateTime->timestamp($this->dateTime->date('Y-m-d 00:00:00', $timeStamp)); + } + return $timeStamp; + } } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php index 5f63283df6760..d838d195acd28 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php @@ -11,6 +11,7 @@ use Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice; use Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor; use Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder; +use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Store\Api\Data\GroupInterface; use Magento\Store\Api\Data\WebsiteInterface; @@ -40,37 +41,45 @@ class ReindexRuleProductPriceTest extends \PHPUnit\Framework\TestCase private $productPriceCalculatorMock; /** - * @var TimezoneInterface|MockObject + * @var DateTime|MockObject */ - private $localeDate; + private $dateTimeMock; /** * @var RuleProductPricesPersistor|MockObject */ private $pricesPersistorMock; + /** + * @var TimezoneInterface|MockObject + */ + private $localeDate; + protected function setUp() { $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); $this->ruleProductsSelectBuilderMock = $this->createMock(RuleProductsSelectBuilder::class); $this->productPriceCalculatorMock = $this->createMock(ProductPriceCalculator::class); - $this->localeDate = $this->createMock(TimezoneInterface::class); + $this->dateTimeMock = $this->createMock(DateTime::class); $this->pricesPersistorMock = $this->createMock(RuleProductPricesPersistor::class); + $this->localeDate = $this->createMock(TimezoneInterface::class); $this->model = new ReindexRuleProductPrice( $this->storeManagerMock, $this->ruleProductsSelectBuilderMock, $this->productPriceCalculatorMock, - $this->localeDate, - $this->pricesPersistorMock + $this->dateTimeMock, + $this->pricesPersistorMock, + $this->localeDate ); } public function testExecute() { $websiteId = 234; - $defaultGroupId = 11; - $defaultStoreId = 22; + $storeGroupId = 30; + $storeId = 40; + $productMock = $this->createMock(Product::class); $websiteMock = $this->createMock(WebsiteInterface::class); $websiteMock->expects($this->once()) @@ -78,22 +87,19 @@ public function testExecute() ->willReturn($websiteId); $websiteMock->expects($this->once()) ->method('getDefaultGroupId') - ->willReturn($defaultGroupId); + ->willReturn($storeGroupId); $this->storeManagerMock->expects($this->once()) ->method('getWebsites') ->willReturn([$websiteMock]); - $groupMock = $this->createMock(GroupInterface::class); - $groupMock->method('getId') - ->willReturn($defaultStoreId); - $groupMock->expects($this->once()) + $storeGroupMock = $this->createMock(GroupInterface::class); + $storeGroupMock->expects($this->once()) ->method('getDefaultStoreId') - ->willReturn($defaultStoreId); + ->willReturn($storeId); $this->storeManagerMock->expects($this->once()) ->method('getGroup') - ->with($defaultGroupId) - ->willReturn($groupMock); + ->with($storeGroupId) + ->willReturn($storeGroupMock); - $productMock = $this->createMock(Product::class); $statementMock = $this->createMock(\Zend_Db_Statement_Interface::class); $this->ruleProductsSelectBuilderMock->expects($this->once()) ->method('build') @@ -109,10 +115,22 @@ public function testExecute() 'action_stop' => true ]; - $this->localeDate->expects($this->once()) - ->method('scopeDate') - ->with($defaultStoreId, null, true) - ->willReturn(new \DateTime()); + $this->dateTimeMock->expects($this->at(0)) + ->method('date') + ->with('Y-m-d 00:00:00', $ruleData['from_time']) + ->willReturn($ruleData['from_time']); + $this->dateTimeMock->expects($this->at(1)) + ->method('timestamp') + ->with($ruleData['from_time']) + ->willReturn($ruleData['from_time']); + $this->dateTimeMock->expects($this->at(2)) + ->method('date') + ->with('Y-m-d 00:00:00', $ruleData['to_time']) + ->willReturn($ruleData['to_time']); + $this->dateTimeMock->expects($this->at(3)) + ->method('timestamp') + ->with($ruleData['to_time']) + ->willReturn($ruleData['to_time']); $statementMock->expects($this->at(0)) ->method('fetch') From 86434a200dc60ab7293c7db51a0667fc98eca953 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Fri, 16 Aug 2019 10:58:01 +0700 Subject: [PATCH 239/841] Resolve Wrong Label when create new "Attribute Set" (issue 24147) --- .../Product/Attribute/Set/Main/Formset.php | 13 ++++++++++++- app/code/Magento/Catalog/i18n/en_US.csv | 2 ++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php index 295ea141b8eef..4f3b13a5ac98e 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php @@ -3,10 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Set\Main; use Magento\Backend\Block\Widget\Form; +/** + * Form attribute set + * + * Class \Magento\Catalog\Block\Adminhtml\Product\Attribute\Set\Main\Formset + */ class Formset extends \Magento\Backend\Block\Widget\Form\Generic { /** @@ -43,7 +50,11 @@ protected function _prepareForm() /** @var \Magento\Framework\Data\Form $form */ $form = $this->_formFactory->create(); - $fieldset = $form->addFieldset('set_name', ['legend' => __('Edit Attribute Set Name')]); + if (!$this->getRequest()->getParam('id', false)) { + $fieldset = $form->addFieldset('set_name', ['legend' => __('Attribute Set Information')]); + } else { + $fieldset = $form->addFieldset('set_name', ['legend' => __('Edit Attribute Set Name')]); + } $fieldset->addField( 'attribute_set_name', 'text', diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv index fa34b088c0f6b..9b7f8a2b07730 100644 --- a/app/code/Magento/Catalog/i18n/en_US.csv +++ b/app/code/Magento/Catalog/i18n/en_US.csv @@ -813,3 +813,5 @@ Details,Details "Edit Category Design","Edit Category Design" "A total of %1 record(s) haven't been deleted. Please see server logs for more details.","A total of %1 record(s) haven't been deleted. Please see server logs for more details." "Are you sure you want to delete this category?","Are you sure you want to delete this category?" +"Attribute Set Information","Attribute Set Information" + From 86d5c2254cf3cd50051f83c30fd873c0657e6977 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Fri, 16 Aug 2019 15:20:57 +0700 Subject: [PATCH 240/841] Resolve Console error when clicking checkbox at "Newsletter Problems Report" (issue 24102) --- .../Newsletter/view/adminhtml/templates/problem/list.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml b/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml index 29382930df3e6..b697be4cf753a 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml +++ b/app/code/Magento/Newsletter/view/adminhtml/templates/problem/list.phtml @@ -17,7 +17,7 @@ require(["prototype", "mage/adminhtml/events"], function(){ problemController = { checkCheckboxes:function (controlCheckbox) { - var elements = $('problemGrid').getElementsByClassName('problemCheckbox'); + var elements = $$('input.problemCheckbox'); if (elements && elements.length) { elements.each(function (obj) { obj.checked = controlCheckbox.checked; From 8e57b691fc4cea336a3fe74d8e89aa98eccb4fc5 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Fri, 16 Aug 2019 15:30:05 +0700 Subject: [PATCH 241/841] refactor source code issue 24147 --- .../Product/Attribute/Set/Main/Formset.php | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php index 4f3b13a5ac98e..484f630d4d03e 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formset.php @@ -50,11 +50,7 @@ protected function _prepareForm() /** @var \Magento\Framework\Data\Form $form */ $form = $this->_formFactory->create(); - if (!$this->getRequest()->getParam('id', false)) { - $fieldset = $form->addFieldset('set_name', ['legend' => __('Attribute Set Information')]); - } else { - $fieldset = $form->addFieldset('set_name', ['legend' => __('Edit Attribute Set Name')]); - } + $fieldset = $form->addFieldset('set_name', ['legend' => $this->getAttributeSetLabel()]); $fieldset->addField( 'attribute_set_name', 'text', @@ -95,4 +91,18 @@ protected function _prepareForm() $form->setOnsubmit('return false;'); $this->setForm($form); } + + /** + * Get Attribute Set Label + * + * @return \Magento\Framework\Phrase + */ + private function getAttributeSetLabel() + { + if ($this->getRequest()->getParam('id', false)) { + return __('Edit Attribute Set Name'); + } + + return __('Attribute Set Information'); + } } From 85902fb5a0825e5981e4d967db51d3bc7725165c Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Fri, 16 Aug 2019 10:26:24 +0400 Subject: [PATCH 242/841] MAGETWO-98748: Incorrect behavior in the category menu on the Storefront - Added automated test script --- .../AdminCategorySidebarTreeSection.xml | 2 + ...goryHighlightedAndProductDisplayedTest.xml | 73 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index fba28b3feaff1..bc552721e6ab8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -11,6 +11,8 @@ <section name="AdminCategorySidebarTreeSection"> <element name="collapseAll" type="button" selector=".tree-actions a:first-child"/> <element name="expandAll" type="button" selector=".tree-actions a:last-child"/> + <element name="categoryHighlighted" type="text" selector="//div[@id='store.menu']//span[contains(text(),'{{name}}')]/ancestor::li" parameterized="true" timeout="30"/> + <element name="categoryNotHighlighted" type="text" selector="ul[id=\'ui-id-2\'] li[class~=\'active\']" timeout="30"/> <element name="categoryTreeRoot" type="text" selector="div.x-tree-root-node>li.x-tree-node:first-of-type>div.x-tree-node-el:first-of-type" timeout="30"/> <element name="categoryInTree" type="text" selector="//a/span[contains(text(), '{{name}}')]" parameterized="true" timeout="30"/> <element name="categoryInTreeUnderRoot" type="text" selector="//li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{name}}')]" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml new file mode 100644 index 0000000000000..7d09cb419f312 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CheckCurrentCategoryIsHighlightedAndProductsDisplayed"> + <annotations> + <features value="Catalog"/> + <stories value="Category"/> + <title value="Сheck that current category is highlighted and all products displayed for it"/> + <description value="Сheck that current category is highlighted and all products displayed for it"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-99028"/> + <useCaseId value="MAGETWO-98748"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="category1"/> + <createData entity="SimpleSubCategory" stepKey="category2"/> + <createData entity="SimpleSubCategory" stepKey="category3"/> + <createData entity="SimpleSubCategory" stepKey="category4"/> + <createData entity="SimpleProduct" stepKey="product1"> + <requiredEntity createDataKey="category1"/> + </createData> + <createData entity="SimpleProduct" stepKey="product2"> + <requiredEntity createDataKey="category1"/> + </createData> + </before> + <after> + <deleteData createDataKey="product1" stepKey="deleteProduct1"/> + <deleteData createDataKey="product2" stepKey="deleteProduct2"/> + <deleteData createDataKey="category1" stepKey="deleteCategory1"/> + <deleteData createDataKey="category2" stepKey="deleteCategory2"/> + <deleteData createDataKey="category3" stepKey="deleteCategory3"/> + <deleteData createDataKey="category4" stepKey="deleteCategory4"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Open Storefront home page--> + <comment userInput="Open Storefront home page" stepKey="openStorefrontHomePage"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontHomePage"/> + <waitForPageLoad stepKey="waitForSimpleProductPage"/> + <!--Click on first category--> + <comment userInput="Click on first category" stepKey="openFirstCategoryPage"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$category1.name$$)}}" stepKey="clickCategory1Name"/> + <waitForPageLoad stepKey="waitForCategory1Page"/> + <!--Check if current category is highlighted and the others are not--> + <comment userInput="Check if current category is highlighted and the others are not" stepKey="checkCateg1NameIsHighlighted"/> + <grabAttributeFrom selector="{{AdminCategorySidebarTreeSection.categoryHighlighted($$category1.name$$)}}" userInput="class" stepKey="grabCategory1Class"/> + <assertContains expectedType="string" expected="active" actual="$grabCategory1Class" stepKey="assertCategory1IsHighlighted"/> + <executeJS function="return document.querySelectorAll('{{AdminCategorySidebarTreeSection.categoryNotHighlighted}}').length" stepKey="highlightedAmount"/> + <assertEquals expectedType="int" expected="1" actual="$highlightedAmount" stepKey="assertRestCategories1IsNotHighlighted"/> + <!--See products in the category page--> + <comment userInput="See products in the category page" stepKey="seeProductsInCategoryPage"/> + <seeElement selector="{{StorefrontCategoryMainSection.specifiedProductItemInfo($product1.name$)}}" stepKey="seeProduct1InCategoryPage"/> + <seeElement selector="{{StorefrontCategoryMainSection.specifiedProductItemInfo($product2.name$)}}" stepKey="seeProduct2InCategoryPage"/> + <!--Click on second category--> + <comment userInput="Click on second category" stepKey="openSecondCategoryPage"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$category2.name$$)}}" stepKey="clickCategory2Name"/> + <waitForPageLoad stepKey="waitForCategory2Page"/> + <!--Check if current category is highlighted and the others are not--> + <comment userInput="Check if current category is highlighted and the others are not" stepKey="checkCateg2NameIsHighlighted"/> + <grabAttributeFrom selector="{{AdminCategorySidebarTreeSection.categoryHighlighted($$category2.name$$)}}" userInput="class" stepKey="grabCategory2Class"/> + <assertContains expectedType="string" expected="active" actual="$grabCategory2Class" stepKey="assertCategory2IsHighlighted"/> + <executeJS function="return document.querySelectorAll('{{AdminCategorySidebarTreeSection.categoryNotHighlighted}}').length" stepKey="highlightedAmount2"/> + <assertEquals expectedType="int" expected="1" actual="$highlightedAmount2" stepKey="assertRestCategories1IsNotHighlighted2"/> + </test> +</tests> From 06c78974684e38858fa28b2763b63ee3ff708307 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 16 Aug 2019 08:55:06 -0500 Subject: [PATCH 243/841] MC-19213: Category Specific URL Rewrite is not getting generated while importing and assigning the product to Category --- .../Observer/AfterImportDataObserver.php | 16 +++-- .../Model/Import/ProductTest.php | 58 +++++++++++++++++++ .../products_to_import_with_category.csv | 2 + 3 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_category.csv diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index 33c0cafc8f081..060730034aaa2 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -299,12 +299,16 @@ protected function _populateForUrlGeneration($rowData) */ private function isNeedToPopulateForUrlGeneration($rowData, $newSku, $oldSku): bool { - if ((empty($newSku) || !isset($newSku['entity_id'])) - || ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE - && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE])) - || (array_key_exists($rowData[ImportProduct::COL_SKU], $oldSku) - && !isset($rowData[self::URL_KEY_ATTRIBUTE_CODE]) - && $this->import->getBehavior() === ImportExport::BEHAVIOR_APPEND)) { + if (( + (empty($newSku) || !isset($newSku['entity_id'])) + || ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE + && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE])) + || (array_key_exists(strtolower($rowData[ImportProduct::COL_SKU]), $oldSku) + && !isset($rowData[self::URL_KEY_ATTRIBUTE_CODE]) + && $this->import->getBehavior() === ImportExport::BEHAVIOR_APPEND) + ) + && !isset($rowData["categories"]) + ) { return false; } return true; diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index c9a08c7f8a11d..42c53366e631c 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -25,6 +25,7 @@ use Magento\Framework\Registry; use Magento\ImportExport\Model\Import; use Magento\Store\Model\Store; +use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection; use Psr\Log\LoggerInterface; use Magento\ImportExport\Model\Import\Source\Csv; @@ -1647,6 +1648,63 @@ public function testImportWithNonExistingImage() } } + /** + * @magentoDataFixture Magento/Catalog/_files/product_without_options.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testUpdateUrlRewritesOnImport() + { + $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/products_to_import_with_category.csv', + 'directory' => $directory + ] + ); + $errors = $this->_model->setParameters( + [ + 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, + 'entity' => \Magento\Catalog\Model\Product::ENTITY + ] + )->setSource( + $source + )->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 0); + + $this->_model->importData(); + + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->objectManager->create(\Magento\Catalog\Model\ProductRepository::class)->get('simple'); + + $repUrlRewriteCol = $this->objectManager->create( + UrlRewriteCollection::class + ); + + /** @var UrlRewriteCollection $collUrlRewrite */ + $collUrlRewrite = $repUrlRewriteCol->addFieldToSelect(['request_path']) + ->addFieldToFilter('entity_id', ['eq'=> $product->getEntityId()]) + ->addFieldToFilter('entity_type', ['eq'=> 'product']) + ->load(); + + $this->assertCount(2, $collUrlRewrite); + + $this->assertEquals( + sprintf('%s.html', $product->getUrlKey()), + $collUrlRewrite->getFirstItem()->getRequestPath() + ); + + $this->assertContains( + sprintf('men/tops/%s.html', $product->getUrlKey()), + $collUrlRewrite->getLastItem()->getRequestPath() + ); + } + /** * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php * @magentoDbIsolation enabled diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_category.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_category.csv new file mode 100644 index 0000000000000..fec3f049737e9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_category.csv @@ -0,0 +1,2 @@ +sku,store_view_code,attribute_set_code,product_type,categories +simple,default,Default,simple,Default Category/Men/Tops \ No newline at end of file From 55ce7770c376833c3771005043ba7d6566994f64 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Fri, 16 Aug 2019 17:31:03 +0300 Subject: [PATCH 244/841] MC-18194: Saved Credit Card Feature with Vault is not displaying proper information of card in order information page --- app/code/Magento/Vault/Model/Method/Vault.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index 5b90ecc86750d..2961dbfb4ec0d 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -122,7 +122,7 @@ class Vault implements VaultPaymentInterface * @param PaymentTokenManagementInterface $tokenManagement * @param OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory * @param string $code - * @param Json $jsonSerializer + * @param Json|null $jsonSerializer * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -137,7 +137,7 @@ public function __construct( PaymentTokenManagementInterface $tokenManagement, OrderPaymentExtensionInterfaceFactory $paymentExtensionFactory, $code, - Json $jsonSerializer + Json $jsonSerializer = null ) { $this->config = $config; $this->configFactory = $configFactory; @@ -149,7 +149,7 @@ public function __construct( $this->tokenManagement = $tokenManagement; $this->paymentExtensionFactory = $paymentExtensionFactory; $this->code = $code; - $this->jsonSerializer = $jsonSerializer; + $this->jsonSerializer = $jsonSerializer ?: $this->objectManager->get(Json::class); } /** From 43e454de018e7c0b54773f0d88cf8412fd4a5356 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Fri, 16 Aug 2019 10:28:13 -0500 Subject: [PATCH 245/841] MC-19090: Category Image from Gallery is not saved --- .../Category/Attribute/Backend/Image.php | 1 + .../Test/Unit/Model/Category/FileInfoTest.php | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index 32b5d066193ca..4af2df37dea0d 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -121,6 +121,7 @@ public function beforeSave($object) if ($this->fileResidesOutsideCategoryDir($value)) { // use relative path for image attribute so we know it's outside of category dir when we fetch it + $value[0]['url'] = parse_url($value[0]['url'], PHP_URL_PATH); $value[0]['name'] = $value[0]['url']; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php index 6c6a69ec39c85..71f5ca33d1303 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php @@ -13,6 +13,8 @@ use Magento\Framework\Filesystem\Directory\WriteInterface; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Store; /** * Test for Magento\Catalog\Model\Category\FileInfo class. @@ -44,6 +46,16 @@ class FileInfoTest extends TestCase */ private $pubDirectory; + /** + * @var StoreManagerInterface|MockObject + */ + private $storeManager; + + /** + * @var Store|MockObject + */ + private $store; + /** * @var FileInfo */ @@ -60,6 +72,16 @@ protected function setUp() $this->pubDirectory = $pubDirectory = $this->getMockBuilder(ReadInterface::class) ->getMockForAbstractClass(); + $this->store = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) + ->setMethods(['getStore']) + ->getMockForAbstractClass(); + $this->storeManager->expects($this->any()) + ->method('getStore') + ->willReturn($this->store); + $this->filesystem = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() ->getMock(); @@ -94,7 +116,8 @@ function ($arg) use ($baseDirectory, $pubDirectory) { $this->model = new FileInfo( $this->filesystem, - $this->mime + $this->mime, + $this->storeManager ); } From af8d6f4736cb38d0ad619ad1e353700797063b4a Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 16 Aug 2019 10:36:54 -0500 Subject: [PATCH 246/841] MC-19213: Category Specific URL Rewrite is not getting generated while importing and assigning the product to Category --- .../CatalogUrlRewrite/Observer/AfterImportDataObserver.php | 2 +- .../Magento/CatalogImportExport/Model/Import/ProductTest.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index 060730034aaa2..704b60a8aaf2a 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -481,7 +481,7 @@ protected function currentUrlRewritesRegenerate() $url = $currentUrlRewrite->getIsAutogenerated() ? $this->generateForAutogenerated($currentUrlRewrite, $category) : $this->generateForCustom($currentUrlRewrite, $category); - $urlRewrites = array_merge($urlRewrites, $url); + $urlRewrites = $url + $urlRewrites; } $this->product = null; diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 42c53366e631c..08f98af820147 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -627,6 +627,7 @@ function ($input) { explode(',', $optionData) ) ); + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $option = array_merge(...$option); if (!empty($option['type']) && !empty($option['name'])) { @@ -693,12 +694,14 @@ protected function mergeWithExistingData( } } else { $existingOptionId = array_search($optionKey, $expectedOptions); + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $expectedData[$existingOptionId] = array_merge( $this->getOptionData($option), $expectedData[$existingOptionId] ); if ($optionValues) { foreach ($optionValues as $optionKey => $optionValue) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $expectedValues[$existingOptionId][$optionKey] = array_merge( $optionValue, $expectedValues[$existingOptionId][$optionKey] From 8072515c720734c42071c1b78b44c007a0731238 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Fri, 16 Aug 2019 10:51:39 -0500 Subject: [PATCH 247/841] MC-19090: Category Image from Gallery is not saved --- .../Magento/Catalog/Model/Category/Attribute/Backend/Image.php | 1 + app/code/Magento/Catalog/Model/Category/FileInfo.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index 4af2df37dea0d..4880214e5c6a6 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -121,6 +121,7 @@ public function beforeSave($object) if ($this->fileResidesOutsideCategoryDir($value)) { // use relative path for image attribute so we know it's outside of category dir when we fetch it + // phpcs:ignore Magento2.Functions.DiscouragedFunction $value[0]['url'] = parse_url($value[0]['url'], PHP_URL_PATH); $value[0]['name'] = $value[0]['url']; } diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php index 0973db756df87..66a251f7ed448 100644 --- a/app/code/Magento/Catalog/Model/Category/FileInfo.php +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -213,7 +213,9 @@ private function removeStorePath(string $path): string } catch (NoSuchEntityException $e) { return $result; } + // phpcs:ignore Magento2.Functions.DiscouragedFunction $path = parse_url($path, PHP_URL_PATH); + // phpcs:ignore Magento2.Functions.DiscouragedFunction $storePath = parse_url($storeUrl, PHP_URL_PATH); $result = ltrim($path, $storePath); From 843081bbdd23727c9cc2722963c09183fa773bcd Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Fri, 16 Aug 2019 11:10:58 -0500 Subject: [PATCH 248/841] magento/magento2#23819: Code style fix --- .../Setup/Declaration/Schema/Dto/Factories/Integer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php index e33cd0927d473..b35e2c9864e83 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/Dto/Factories/Integer.php @@ -50,7 +50,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function create(array $data) { From cd259746c3b6cc167b20bafdd507dc2282ded923 Mon Sep 17 00:00:00 2001 From: Dmytro Poperechnyy <dpoperechnyy@magento.com> Date: Fri, 16 Aug 2019 13:20:36 -0500 Subject: [PATCH 249/841] MC-19243: Fix phtml files with mage-init directives that cannot be statically analyzed --- .../frontend/templates/paypal/button.phtml | 15 +++----- .../templates/product/breadcrumbs.phtml | 10 ++++- .../templates/product/list/toolbar.phtml | 5 ++- .../frontend/templates/js/customer-data.phtml | 21 +++++----- .../js/customer-data/invalidation-rules.phtml | 29 +++++++------- .../templates/js/section-config.phtml | 23 +++++------ .../base/templates/product/price/msrp.phtml | 10 ++--- .../express/in-context/component.phtml | 38 +++++++++---------- .../express/in-context/shortcut/button.phtml | 6 ++- .../templates/express/shortcut_button.phtml | 8 +++- .../frontend/templates/html/sections.phtml | 3 +- 11 files changed, 91 insertions(+), 77 deletions(-) diff --git a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml index 36eddcf5819d9..19dfed0255085 100644 --- a/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml +++ b/app/code/Magento/Braintree/view/frontend/templates/paypal/button.phtml @@ -11,17 +11,14 @@ $id = $block->getContainerId() . random_int(0, PHP_INT_MAX); $config = [ - 'Magento_Braintree/js/paypal/button' => [ - 'id' => $id, - 'clientToken' => $block->getClientToken(), - 'displayName' => $block->getMerchantName(), - 'actionSuccess' => $block->getActionSuccess(), - 'environment' => $block->getEnvironment() - ] + 'id' => $id, + 'clientToken' => $block->getClientToken(), + 'displayName' => $block->getMerchantName(), + 'actionSuccess' => $block->getActionSuccess(), + 'environment' => $block->getEnvironment() ]; - ?> -<div data-mage-init='<?= /* @noEscape */ json_encode($config); ?>' +<div data-mage-init='{"Magento_Braintree/js/paypal/button":<?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($config) ?>}' class="paypal checkout paypal-logo braintree-paypal-logo<?= /* @noEscape */ $block->getContainerId(); ?>-container"> <div data-currency="<?= /* @noEscape */ $block->getCurrency(); ?>" data-locale="<?= /* @noEscape */ $block->getLocale(); ?>" diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml index c4aa84704b598..fd18db94fdc88 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/breadcrumbs.phtml @@ -8,8 +8,14 @@ $viewModel = $block->getData('viewModel'); ?> <div class="breadcrumbs"></div> +<?php +$widget = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonDecode($viewModel->getJsonConfigurationHtmlEscaped()); +$widgetOptions = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($widget['breadcrumbs']); +?> <script type="text/x-magento-init"> - { - ".breadcrumbs": <?= $viewModel->getJsonConfigurationHtmlEscaped() ?> + { + ".breadcrumbs": { + "breadcrumbs": <?= /* @noEscape */ $widgetOptions ?> } + } </script> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml index b2ae8b9f7ab13..76ef6baf4993e 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml @@ -15,7 +15,10 @@ // phpcs:disable PSR2.Methods.FunctionCallSignature.SpaceBeforeOpenBracket ?> <?php if ($block->getCollection()->getSize()) :?> - <div class="toolbar toolbar-products" data-mage-init='<?= /* @noEscape */ $block->getWidgetOptionsJson() ?>'> + <?php $widget = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonDecode($block->getWidgetOptionsJson()); + $widgetOptions = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($widget['productListToolbarForm']); + ?> + <div class="toolbar toolbar-products" data-mage-init='{"productListToolbarForm":<?= /* @noEscape */ $widgetOptions ?>}'> <?php if ($block->isExpanded()) :?> <?php include ($block->getTemplateFile('Magento_Catalog::product/list/toolbar/viewmode.phtml')) ?> <?php endif; ?> diff --git a/app/code/Magento/Customer/view/frontend/templates/js/customer-data.phtml b/app/code/Magento/Customer/view/frontend/templates/js/customer-data.phtml index 0373b7be4c71b..a1a784076bac3 100644 --- a/app/code/Magento/Customer/view/frontend/templates/js/customer-data.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/js/customer-data.phtml @@ -7,14 +7,15 @@ /** @var \Magento\Customer\Block\CustomerData $block */ ?> <script type="text/x-magento-init"> -<?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode([ - '*' => ['Magento_Customer/js/customer-data' => [ - 'sectionLoadUrl' => $block->getCustomerDataUrl('customer/section/load'), - 'expirableSectionLifetime' => $block->getExpirableSectionLifetime(), - 'expirableSectionNames' => $block->getExpirableSectionNames(), - 'cookieLifeTime' => $block->getCookieLifeTime(), - 'updateSessionUrl' => $block->getCustomerDataUrl('customer/account/updateSession'), - ]], - ]); -?> + { + "*": { + "Magento_Customer/js/customer-data": { + "sectionLoadUrl": "<?= $block->escapeJs($block->escapeUrl($block->getCustomerDataUrl('customer/section/load'))) ?>", + "expirableSectionLifetime": <?= (int)$block->getExpirableSectionLifetime() ?>, + "expirableSectionNames": <?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getExpirableSectionNames()) ?>, + "cookieLifeTime": "<?= $block->escapeJs($block->getCookieLifeTime()) ?>", + "updateSessionUrl": "<?= $block->escapeJs($block->escapeUrl($block->getCustomerDataUrl('customer/account/updateSession'))) ?>" + } + } + } </script> diff --git a/app/code/Magento/Customer/view/frontend/templates/js/customer-data/invalidation-rules.phtml b/app/code/Magento/Customer/view/frontend/templates/js/customer-data/invalidation-rules.phtml index 15d0f52265770..4f8a917d1ed62 100644 --- a/app/code/Magento/Customer/view/frontend/templates/js/customer-data/invalidation-rules.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/js/customer-data/invalidation-rules.phtml @@ -7,18 +7,19 @@ /* @var $block \Magento\Customer\Block\CustomerScopeData */ ?> <script type="text/x-magento-init"> -<?= /* @noEscape */ $block->encodeConfiguration([ - '*' => ['Magento_Customer/js/invalidation-processor' => [ - 'invalidationRules' => [ - 'website-rule' => [ - 'Magento_Customer/js/invalidation-rules/website-rule' => [ - 'scopeConfig' => [ - 'websiteId' => $block->getWebsiteId(), - ] - ] - ] - ] - ]], - ]); -?> + { + "*": { + "Magento_Customer/js/invalidation-processor": { + "invalidationRules": { + "website-rule": { + "Magento_Customer/js/invalidation-rules/website-rule": { + "scopeConfig": { + "websiteId": "<?= $block->escapeJs($block->getWebsiteId()) ?>" + } + } + } + } + } + } + } </script> diff --git a/app/code/Magento/Customer/view/frontend/templates/js/section-config.phtml b/app/code/Magento/Customer/view/frontend/templates/js/section-config.phtml index 94ab4757898cf..ebbd16164d7e8 100644 --- a/app/code/Magento/Customer/view/frontend/templates/js/section-config.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/js/section-config.phtml @@ -7,15 +7,16 @@ /** @var \Magento\Customer\Block\SectionConfig $block */ ?> <script type="text/x-magento-init"> -<?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode([ - '*' => ['Magento_Customer/js/section-config' => [ - 'sections' => $block->getSections(), - 'clientSideSections' => $block->getClientSideSections(), - 'baseUrls' => array_unique([ - $block->getUrl(null, ['_secure' => true]), - $block->getUrl(null, ['_secure' => false]), - ]), - ]], - ]); -?> + { + "*": { + "Magento_Customer/js/section-config": { + "sections": <?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getSections()) ?>, + "clientSideSections": <?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($block->getClientSideSections()) ?>, + "baseUrls": <?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode(array_unique([ + $block->getUrl(null, ['_secure' => true]), + $block->getUrl(null, ['_secure' => false]), + ])) ?> + } + } + } </script> diff --git a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml index 2ab40a7ec8299..b062e911876c3 100644 --- a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml +++ b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml @@ -59,7 +59,7 @@ $priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElem $priceElementId = $priceElementIdPrefix . $productId . $block->getIdSuffix(); $popupId = 'msrp-popup-' . $productId . $block->getRandomString(20); - $data = ['addToCart' => [ + $data = [ 'origin'=> 'msrp', 'popupId' => '#' . $popupId, 'productName' => $block->escapeJs($block->escapeHtml($product->getName())), @@ -72,11 +72,11 @@ $priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElem 'closeButtonId' => '#map-popup-close', 'addToCartUrl' => $addToCartUrl, 'paymentButtons' => '[data-label=or]' - ]]; + ]; if ($block->getRequest()->getFullActionName() === 'catalog_product_view') { - $data['addToCart']['addToCartButton'] = '#product_addtocart_form [type=submit]'; + $data['addToCartButton'] = '#product_addtocart_form [type=submit]'; } else { - $data['addToCart']['addToCartButton'] = sprintf( + $data['addToCartButton'] = sprintf( 'form:has(input[type="hidden"][name="product"][value="%s"]) button[type="submit"]', (int) $productId . ',' . sprintf( @@ -91,7 +91,7 @@ $priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElem id="<?= /* @noEscape */ ($popupId) ?>" class="action map-show-info" <?php //phpcs:disable ?> - data-mage-init='<?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($data) ?>'> + data-mage-init='{"addToCart":<?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($data) ?>}'> <?php //phpcs:enable ?> <?= $block->escapeHtml(__('Click for price')) ?> </a> diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/component.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/in-context/component.phtml index 6efb678ffdae6..ba98f5caeb15a 100644 --- a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/component.phtml +++ b/app/code/Magento/Paypal/view/frontend/templates/express/in-context/component.phtml @@ -8,24 +8,20 @@ use Magento\Paypal\Block\Express\InContext\Minicart\Button; /** @var \Magento\Paypal\Block\Express\InContext\Component $block */ $configuration = [ - '*' => [ - 'Magento_Paypal/js/in-context/express-checkout' => [ - 'id' => Button::PAYPAL_BUTTON_ID, - 'path' => $block->getUrl( - 'paypal/express/gettoken', - [ - '_secure' => $block->getRequest()->isSecure() - ] - ), - 'merchantId' => $block->getMerchantId(), - 'button' => $block->isButtonContext(), - 'clientConfig' => [ - 'locale' => $block->getLocale(), - 'environment' => $block->getEnvironment(), - 'button' => [ - Button::PAYPAL_BUTTON_ID, - ], - ] + 'id' => Button::PAYPAL_BUTTON_ID, + 'path' => $block->getUrl( + 'paypal/express/gettoken', + [ + '_secure' => $block->getRequest()->isSecure() + ] + ), + 'merchantId' => $block->getMerchantId(), + 'button' => $block->isButtonContext(), + 'clientConfig' => [ + 'locale' => $block->getLocale(), + 'environment' => $block->getEnvironment(), + 'button' => [ + Button::PAYPAL_BUTTON_ID, ] ] ]; @@ -33,5 +29,9 @@ $configuration = [ ?> <div style="display: none;" id="<?= /* @noEscape */ Button::PAYPAL_BUTTON_ID ?>"></div> <script type="text/x-magento-init"> - <?= /* @noEscape */ json_encode($configuration) ?> + { + "*": { + "Magento_Paypal/js/in-context/express-checkout": <?= /* @noEscape */ $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($configuration) ?> + } + } </script> diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml index 66dddfb0bda95..86c3c5c25f0bc 100644 --- a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml +++ b/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml @@ -7,7 +7,9 @@ /** * @var \Magento\Paypal\Block\Express\InContext\Minicart\SmartButton $block */ +$widget = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonDecode($block->getJsInitParams()); +$widgetConfig = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($widget['Magento_Paypal/js/in-context/button']); ?> -<div data-mage-init='<?= /* @noEscape */ $block->getJsInitParams() ?>' +<div data-mage-init='{"Magento_Paypal/js/in-context/button":<?= /* @noEscape */ $widgetConfig ?>}' class="paypal checkout paypal-logo <?= $block->escapeHtml($block->getContainerId()) ?>-container"> -</div> \ No newline at end of file +</div> diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml index a30bc2cce6d4f..76d034f462a7a 100644 --- a/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml +++ b/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml @@ -5,7 +5,11 @@ */ /** - * @var \Magento\Paypal\Block\Express\Shortcut $block + * @var \Magento\Paypal\Block\Express\InContext\SmartButton $block */ +$widget = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonDecode($block->getJsInitParams()); +$widgetConfig = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode( + $widget['Magento_Paypal/js/in-context/product-express-checkout'] +); ?> -<div data-mage-init='<?= /* @noEscape */ $block->getJsInitParams() ?>'></div> +<div data-mage-init='{"Magento_Paypal/js/in-context/product-express-checkout":<?= /* @noEscape */ $widgetConfig ?>}'></div> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml b/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml index 602749ba04deb..7f1128b3b07c7 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/sections.phtml @@ -10,13 +10,12 @@ $group = $block->getGroupName(); $groupCss = $block->getGroupCss(); -$groupBehavior = $block->getGroupBehaviour() ? $block->getGroupBehaviour() : '{"tabs":{"openedState":"active"}}'; ?> <?php if ($detailedInfoGroup = $block->getGroupChildNames($group, 'getChildHtml')) :?> <div class="sections <?= $block->escapeHtmlAttr($groupCss) ?>"> <?php $layout = $block->getLayout(); ?> <div class="section-items <?= $block->escapeHtmlAttr($groupCss) ?>-items" - data-mage-init='<?= $block->escapeHtmlAttr($groupBehavior) ?>'> + data-mage-init='{"tabs":{"openedState":"active"}}'> <?php foreach ($detailedInfoGroup as $name) :?> <?php $html = $layout->renderElement($name); From bda5529dbd8d96a71a4162b22511833444d5ded1 Mon Sep 17 00:00:00 2001 From: Dusan Lukic <ldusan84@gmail.com> Date: Fri, 16 Aug 2019 21:30:36 +0200 Subject: [PATCH 250/841] Added some stuff missing from schema --- .../DownloadableGraphQl/etc/schema.graphqls | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index 7022fcfc1f3fe..245e2308488ac 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -1,6 +1,35 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. +type Mutation { + addDownloadableProductsToCart(input: AddDownloadableProductsToCartInput): AddDownloadableProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") +} + +input AddDownloadableProductsToCartInput { + cart_id: String! + cart_items: [DownloadableProductCartItemInput!]! +} + +input DownloadableProductCartItemInput { + data: CartItemInput! + downloadable_product_links: [DownloadableProductLinksInput!] + customizable_options:[CustomizableOptionInput!] +} + +input DownloadableProductLinksInput { + link_id: Int! +} + +type AddDownloadableProductsToCartOutput { + cart: Cart! +} + +type DownloadableCartItem implements CartItemInterface @doc(description: "Downloadable Cart Item") { + customizable_options: [SelectedCustomizableOption] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CustomizableOptions") + links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\DownloadableCartItem\\Links") @doc(description: "An array containing information about the links for the added to cart downloadable product") + samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\DownloadableCartItem\\Samples") @doc(description: "DownloadableProductSamples defines characteristics of a downloadable product") +} + type DownloadableProduct implements ProductInterface, CustomizableProductInterface @doc(description: "DownloadableProduct defines a product that the customer downloads") { downloadable_product_samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Product\\Samples") @doc(description: "An array containing information about samples of this downloadable product.") downloadable_product_links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Product\\Links") @doc(description: "An array containing information about the links for this downloadable product") @@ -33,4 +62,4 @@ type DownloadableProductSamples @doc(description: "DownloadableProductSamples de sample_url: String @doc(description: "URL to the downloadable sample") sample_type: DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample") sample_file: String @deprecated(reason: "`sample_url` serves to get the downloadable sample") -} +} \ No newline at end of file From b370b1e5f0005f17ebbe6e637cdb65b51f9710ea Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Fri, 16 Aug 2019 14:58:37 -0500 Subject: [PATCH 251/841] magento/magento2#22478: Move reload logic to the appropriate place --- .../Magento/Checkout/view/frontend/web/js/sidebar.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index be5f29911b4c1..b1a88e15bd4ef 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -261,6 +261,9 @@ define([ $(document).trigger('ajax:removeFromCart', { productIds: [productData['product_id']] }); + if (window.location.href === this.shoppingCartUrl) { + location.reload(); + } } }, @@ -306,14 +309,10 @@ define([ } }) .done(function (response) { - var msg, currentURL; + var msg; if (response.success) { callback.call(this, elem, response); - currentURL = window.location.pathname; - if (currentURL.includes("/checkout/cart/")) { - location.reload(); - } } else { msg = response['error_message']; From 02654a6baea7fd138a4dd731b9544a530dbafe58 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Fri, 16 Aug 2019 21:37:56 +0000 Subject: [PATCH 252/841] Added changes from PR #807 --- .../Magento/Customer/Block/Form/Register.php | 15 ++++-- .../Test/Unit/Block/Form/RegisterTest.php | 18 +++---- app/code/Magento/Newsletter/Model/Config.php | 3 +- .../PredispatchNewsletterObserver.php | 25 ++++++--- .../PredispatchNewsletterObserverTest.php | 52 ++++++++----------- 5 files changed, 61 insertions(+), 52 deletions(-) diff --git a/app/code/Magento/Customer/Block/Form/Register.php b/app/code/Magento/Customer/Block/Form/Register.php index a190ccde50b5a..be16046d69075 100644 --- a/app/code/Magento/Customer/Block/Form/Register.php +++ b/app/code/Magento/Customer/Block/Form/Register.php @@ -6,7 +6,8 @@ namespace Magento\Customer\Block\Form; use Magento\Customer\Model\AccountManagement; -use Magento\Newsletter\Observer\PredispatchNewsletterObserver; +use Magento\Framework\App\ObjectManager; +use Magento\Newsletter\Model\Config; /** * Customer register form block @@ -32,6 +33,11 @@ class Register extends \Magento\Directory\Block\Data */ protected $_customerUrl; + /** + * @var Config + */ + private $newsLetterConfig; + /** * Constructor * @@ -45,6 +51,7 @@ class Register extends \Magento\Directory\Block\Data * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Customer\Model\Url $customerUrl * @param array $data + * @param Config $newsLetterConfig * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -58,11 +65,13 @@ public function __construct( \Magento\Framework\Module\ModuleManagerInterface $moduleManager, \Magento\Customer\Model\Session $customerSession, \Magento\Customer\Model\Url $customerUrl, - array $data = [] + array $data = [], + Config $newsLetterConfig = null ) { $this->_customerUrl = $customerUrl; $this->_moduleManager = $moduleManager; $this->_customerSession = $customerSession; + $this->newsLetterConfig = $newsLetterConfig ?: ObjectManager::getInstance()->get(Config::class); parent::__construct( $context, $directoryHelper, @@ -170,7 +179,7 @@ public function getRegion() public function isNewsletterEnabled() { return $this->_moduleManager->isOutputEnabled('Magento_Newsletter') - && $this->getConfig(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE); + && $this->newsLetterConfig->isActive(); } /** diff --git a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php index b93b9f40d75b2..e1adfdf4ed8f1 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php @@ -7,7 +7,6 @@ use Magento\Customer\Block\Form\Register; use Magento\Customer\Model\AccountManagement; -use Magento\Newsletter\Observer\PredispatchNewsletterObserver; /** * Test class for \Magento\Customer\Block\Form\Register. @@ -49,6 +48,9 @@ class RegisterTest extends \PHPUnit\Framework\TestCase /** @var Register */ private $_block; + /** @var \PHPUnit_Framework_MockObject_MockObject | \Magento\Newsletter\Model\Config */ + private $newsletterConfig; + protected function setUp() { $this->_scopeConfig = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); @@ -59,9 +61,9 @@ protected function setUp() \Magento\Customer\Model\Session::class, ['getCustomerFormData'] ); + $this->newsletterConfig = $this->createMock(\Magento\Newsletter\Model\Config::class); $context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class); $context->expects($this->any())->method('getScopeConfig')->will($this->returnValue($this->_scopeConfig)); - $this->_block = new \Magento\Customer\Block\Form\Register( $context, $this->directoryHelperMock, @@ -71,7 +73,9 @@ protected function setUp() $this->createMock(\Magento\Directory\Model\ResourceModel\Country\CollectionFactory::class), $this->_moduleManager, $this->_customerSession, - $this->_customerUrl + $this->_customerUrl, + [], + $this->newsletterConfig ); } @@ -292,17 +296,13 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $isNewsletterActiv )->will( $this->returnValue($isNewsletterEnabled) ); - - $this->_scopeConfig->expects( + $this->newsletterConfig->expects( $this->any() )->method( - 'getValue' - )->with( - PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE + 'isActive' )->will( $this->returnValue($isNewsletterActive) ); - $this->assertEquals($expectedValue, $this->_block->isNewsletterEnabled()); } diff --git a/app/code/Magento/Newsletter/Model/Config.php b/app/code/Magento/Newsletter/Model/Config.php index c6e4ba36591fc..be1ad759d650d 100644 --- a/app/code/Magento/Newsletter/Model/Config.php +++ b/app/code/Magento/Newsletter/Model/Config.php @@ -38,9 +38,10 @@ public function __construct( /** * Returns newsletter's enabled status * + * @param string $scopeType * @return bool */ - public function isActive($scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT) + public function isActive(string $scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT): bool { return $this->scopeConfig->isSetFlag(self::XML_PATH_NEWSLETTER_ACTIVE, $scopeType); } diff --git a/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php index 9860798b2b9f3..f63520e79496f 100644 --- a/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php +++ b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php @@ -12,6 +12,8 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Framework\UrlInterface; use Magento\Store\Model\ScopeInterface; +use Magento\Newsletter\Model\Config; +use Magento\Framework\App\ObjectManager; /** * Class PredispatchNewsletterObserver @@ -19,10 +21,16 @@ class PredispatchNewsletterObserver implements ObserverInterface { /** - * Configuration path to newsletter active setting + * @deprecated + * @see \Magento\Newsletter\Model\Config::isActive() */ const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; + /** + * @var Config + */ + private $newsletterConfig; + /** * @var ScopeConfigInterface */ @@ -38,11 +46,16 @@ class PredispatchNewsletterObserver implements ObserverInterface * * @param ScopeConfigInterface $scopeConfig * @param UrlInterface $url + * @param Config|null $newsletterConfig */ - public function __construct(ScopeConfigInterface $scopeConfig, UrlInterface $url) - { + public function __construct( + ScopeConfigInterface $scopeConfig, + UrlInterface $url, + Config $newsletterConfig = null + ) { $this->scopeConfig = $scopeConfig; $this->url = $url; + $this->newsletterConfig = $newsletterConfig ?: ObjectManager::getInstance()->get(Config::class); } /** @@ -52,11 +65,7 @@ public function __construct(ScopeConfigInterface $scopeConfig, UrlInterface $url */ public function execute(Observer $observer) : void { - if (!$this->scopeConfig->getValue( - self::XML_PATH_NEWSLETTER_ACTIVE, - ScopeInterface::SCOPE_STORE - ) - ) { + if (!$this->newsletterConfig->isActive(ScopeInterface::SCOPE_STORE)) { $defaultNoRouteUrl = $this->scopeConfig->getValue( 'web/default/no_route', ScopeInterface::SCOPE_STORE diff --git a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php index 38d69e5128af1..f88537397ae66 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php @@ -13,6 +13,7 @@ use Magento\Framework\Event\Observer; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\UrlInterface; +use Magento\Newsletter\Model\Config; use Magento\Newsletter\Observer\PredispatchNewsletterObserver; use Magento\Store\Model\ScopeInterface; use PHPUnit\Framework\TestCase; @@ -52,30 +53,29 @@ class PredispatchNewsletterObserverTest extends TestCase */ private $objectManager; + /** + * @var Config + */ + private $newsletterConfig; + /** * @inheritdoc */ protected function setUp() : void { - $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->urlMock = $this->getMockBuilder(UrlInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->configMock = $this->createMock(ScopeConfigInterface::class); + $this->urlMock = $this->createMock(UrlInterface::class); $this->responseMock = $this->getMockBuilder(ResponseInterface::class) ->disableOriginalConstructor() ->setMethods(['setRedirect']) ->getMockForAbstractClass(); - $this->redirectMock = $this->getMockBuilder(RedirectInterface::class) - ->getMock(); + $this->redirectMock = $this->createMock(RedirectInterface::class); + $this->newsletterConfig = $this->createMock(Config::class); $this->objectManager = new ObjectManager($this); - $this->mockObject = $this->objectManager->getObject( - PredispatchNewsletterObserver::class, - [ - 'scopeConfig' => $this->configMock, - 'url' => $this->urlMock - ] + $this->mockObject = new PredispatchNewsletterObserver( + $this->configMock, + $this->urlMock, + $this->newsletterConfig ); } @@ -88,19 +88,17 @@ public function testNewsletterEnabled() : void ->disableOriginalConstructor() ->setMethods(['getResponse', 'getData', 'setRedirect']) ->getMockForAbstractClass(); - - $this->configMock->method('getValue') - ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE) + $this->newsletterConfig->expects($this->once()) + ->method('isActive') + ->with(ScopeInterface::SCOPE_STORE) ->willReturn(true); $observerMock->expects($this->never()) ->method('getData') ->with('controller_action') ->willReturnSelf(); - $observerMock->expects($this->never()) ->method('getResponse') ->willReturnSelf(); - $this->assertNull($this->mockObject->execute($observerMock)); } @@ -113,35 +111,27 @@ public function testNewsletterDisabled() : void ->disableOriginalConstructor() ->setMethods(['getControllerAction', 'getResponse']) ->getMockForAbstractClass(); - - $this->configMock->expects($this->at(0)) - ->method('getValue') - ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE) + $this->newsletterConfig->expects($this->once()) + ->method('isActive') + ->with(ScopeInterface::SCOPE_STORE) ->willReturn(false); - $expectedRedirectUrl = 'https://test.com/index'; - - $this->configMock->expects($this->at(1)) + $this->configMock->expects($this->once()) ->method('getValue') ->with('web/default/no_route', ScopeInterface::SCOPE_STORE) ->willReturn($expectedRedirectUrl); - $this->urlMock->expects($this->once()) ->method('getUrl') ->willReturn($expectedRedirectUrl); - $observerMock->expects($this->once()) ->method('getControllerAction') ->willReturnSelf(); - $observerMock->expects($this->once()) ->method('getResponse') ->willReturn($this->responseMock); - $this->responseMock->expects($this->once()) ->method('setRedirect') ->with($expectedRedirectUrl); - $this->assertNull($this->mockObject->execute($observerMock)); } } From 053123c290fac941009bfcfa0aade416439177a5 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Fri, 16 Aug 2019 16:59:50 -0500 Subject: [PATCH 253/841] MC-19238: Subscribe to Order Status RSS gives error page --- .../Magento/Sales/Model/Rss/OrderStatus.php | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Sales/Model/Rss/OrderStatus.php b/app/code/Magento/Sales/Model/Rss/OrderStatus.php index c3c4456c6b7ca..e03cbbee1b50c 100644 --- a/app/code/Magento/Sales/Model/Rss/OrderStatus.php +++ b/app/code/Magento/Sales/Model/Rss/OrderStatus.php @@ -217,11 +217,12 @@ protected function getEntries() if ($type && $type != 'order') { $urlAppend = $type; } - $type = __(ucwords($type)); - $title = __('Details for %1 #%2', $type, $result['increment_id']); - $description = '<p>' . __('Notified Date: %1', $this->localeDate->formatDate($result['created_at'])) + $type = __(ucwords($type))->render(); + $title = __('Details for %1 #%2', $type, $result['increment_id'])->render(); + $description = '<p>' + . __('Notified Date: %1', $this->localeDate->formatDate($result['created_at']))->render() . '<br/>' - . __('Comment: %1<br/>', $result['comment']) . '</p>'; + . __('Comment: %1<br/>', $result['comment'])->render() . '</p>'; $url = $this->urlBuilder->getUrl( 'sales/order/' . $urlAppend, ['order_id' => $this->order->getId()] @@ -233,10 +234,10 @@ protected function getEntries() 'Order #%1 created at %2', $this->order->getIncrementId(), $this->localeDate->formatDate($this->order->getCreatedAt()) - ); + )->render(); $url = $this->urlBuilder->getUrl('sales/order/view', ['order_id' => $this->order->getId()]); - $description = '<p>' . __('Current Status: %1<br/>', $this->order->getStatusLabel()) . - __('Total: %1<br/>', $this->order->formatPrice($this->order->getGrandTotal())) . '</p>'; + $description = '<p>' . __('Current Status: %1<br/>', $this->order->getStatusLabel())->render() . + __('Total: %1<br/>', $this->order->formatPrice($this->order->getGrandTotal()))->render() . '</p>'; $entries[] = ['title' => $title, 'link' => $url, 'description' => $description]; @@ -250,7 +251,7 @@ protected function getEntries() */ protected function getHeader() { - $title = __('Order # %1 Notification(s)', $this->order->getIncrementId()); + $title = __('Order # %1 Notification(s)', $this->order->getIncrementId())->render(); $newUrl = $this->urlBuilder->getUrl('sales/order/view', ['order_id' => $this->order->getId()]); return ['title' => $title, 'description' => $title, 'link' => $newUrl, 'charset' => 'UTF-8']; From 1b626a590dca46470f73f7bd05c8ad73d2ac0fe5 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Fri, 16 Aug 2019 17:36:28 -0500 Subject: [PATCH 254/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - added configurable attribute use case and added fixture --- .../GraphQl/Catalog/ProductSearchTest.php | 116 +++++++++++++++++- ...th_custom_attribute_layered_navigation.php | 48 ++++++++ ..._attribute_layered_navigation_rollback.php | 9 ++ 3 files changed, 169 insertions(+), 4 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index e607e2d608786..286843e9b53e9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -20,6 +20,7 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\Framework\DataObject; +use Magento\TestFramework\Helper\Bootstrap; /** * @SuppressWarnings(PHPMD.TooManyPublicMethods) @@ -99,12 +100,11 @@ public function testFilterLn() public function testAdvancedSearchByOneCustomAttribute() { $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); - $query = <<<QUERY { - products(filter:{ + products(filter:{ second_test_configurable: {eq: "{$optionValue}"} - }, + } pageSize: 3 currentPage: 1 ) @@ -136,6 +136,8 @@ public function testAdvancedSearchByOneCustomAttribute() } } QUERY; + + /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); $product1 = $productRepository->get('simple'); @@ -194,7 +196,8 @@ private function getDefaultAttributeOptionValue(string $attributeCode) : string $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); /** @var AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); - $defaultOptionValue = $options[1]->getValue(); + array_shift($options); + $defaultOptionValue = $options[0]->getValue(); return $defaultOptionValue; } @@ -393,10 +396,115 @@ public function testFilterByCategoryIdAndCustomAttribute() 'Products count in the category is incorrect' ) ; } + } + private function getQueryProductsWithCustomAttribute($optionValue) + { + return <<<QUERY +{ + products(filter:{ + test_configurable: {eq: "{$optionValue}"} + } + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + + } + + } +} +QUERY; } + /** + * Layered navigation for Configurable products with out of stock options + * Two configurable products each having two variations and one of the child products of one Configurable set to OOS + * + * @magentoApiDataFixture Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testLayeredNavigationForConfigurableProductWithOutOfStockOption() + { + $attributeCode = 'test_configurable'; + /** @var \Magento\Eav\Model\Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); + /** @var AttributeOptionInterface[] $options */ + $options = $attribute->getOptions(); + array_shift($options); + $firstOption = $options[0]->getValue(); + $secondOption = $options[1]->getValue(); + $query = $this->getQueryProductsWithCustomAttribute($firstOption); + $response = $this->graphQlQuery($query); + + //Only 1 product will be returned since only one child product with attribute option1 from 1st Configurable product is OOS + $this->assertEquals(1, $response['products']['total_count']); + + // Custom attribute filter layer data + $this->assertResponseFields( + $response['products']['filters'][1], + [ + 'name' => $attribute->getDefaultFrontendLabel(), + 'request_var'=> $attribute->getAttributeCode(), + 'filter_items_count'=> 2, + 'filter_items' => [ + [ + 'label' => 'Option 1', + 'items_count' => 1, + 'value_string' => $firstOption, + '__typename' =>'LayerFilterItem' + ], + [ + 'label' => 'Option 2', + 'items_count' => 1, + 'value_string' => $secondOption, + '__typename' =>'LayerFilterItem' + ] + ], + ] + ); + + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $outOfStockChildProduct = $productRepository->get('simple_30'); + // Set another child product from 2nd Configurable product with attribute option1 to OOS + $outOfStockChildProduct->setStockData( + ['use_config_manage_stock' => 1, + 'qty' => 0, + 'is_qty_decimal' => 0, + 'is_in_stock' => 0] + ); + $productRepository->save($outOfStockChildProduct); + $query = $this->getQueryProductsWithCustomAttribute($firstOption); + $response = $this->graphQlQuery($query); + $this->assertEquals(0, $response['products']['total_count']); + $this->assertEmpty($response['products']['items']); + $this->assertEmpty($response['products']['filters']); + $i = 0; + } /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php new file mode 100644 index 0000000000000..03a4baabf088e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../ConfigurableProduct/_files/configurable_products.php'; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Api\AttributeRepositoryInterface; + +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + +/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + +$eavConfig->clear(); + +$attribute->setIsSearchable(1) + ->setIsVisibleInAdvancedSearch(1) + ->setIsFilterable(true) + ->setIsFilterableInSearch(true) + ->setIsVisibleOnFront(1); + +/** @var AttributeRepositoryInterface $attributeRepository */ +$attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); +$attributeRepository->save($attribute); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$outOfStockChildProduct = $productRepository->get('simple_10'); +$outOfStockChildProduct->setStockData( + ['use_config_manage_stock' => 1, + 'qty' => 0, + 'is_qty_decimal' => 0, + 'is_in_stock' => 0] +); +$productRepository->save($outOfStockChildProduct); + +/** @var \Magento\Indexer\Model\Indexer\Collection $indexerCollection */ +$indexerCollection = Bootstrap::getObjectManager()->get(\Magento\Indexer\Model\Indexer\Collection::class); +$indexerCollection->load(); +/** @var \Magento\Indexer\Model\Indexer $indexer */ +foreach ($indexerCollection->getItems() as $indexer) { + $indexer->reindexAll(); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php new file mode 100644 index 0000000000000..49e2a8e88a1ac --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../ConfigurableProduct/_files/configurable_products_rollback.php'; From 8189449897ed01a3d532b8ea6390082ef3b1bd47 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Sat, 17 Aug 2019 08:40:19 +0530 Subject: [PATCH 255/841] 24151 : fix js issue for payment config search --- .../view/adminhtml/templates/system/config/edit.phtml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml b/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml index 4b8ebb39beb92..7e7a540e88b2e 100644 --- a/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml +++ b/app/code/Magento/Config/view/adminhtml/templates/system/config/edit.phtml @@ -58,10 +58,16 @@ require([ var parentSection = groupElement.parents('.section-config'); parentSection.addClass('highlighted'); + parentSection.addClass('active'); setTimeout(function() { parentSection.removeClass('highlighted', 2000, "easeInBack"); }, 3000); if (!parentSection.hasClass('active')) { + if(section == 'payment') { + var openSection = jQuery('.open').first().attr('id'); + var splitIdArray = openSection.split('_'); + section = section + '_' + splitIdArray[1]; + } Fieldset.toggleCollapse(section + '_' + group); } } From db48e4c5d3de68323c88f1d540570bb652ae7e20 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Sat, 17 Aug 2019 15:41:17 +0530 Subject: [PATCH 256/841] added backend compatible for variable --- .../Model/Import/AdvancedPricing.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index d883173daeece..bc23bfdf246b5 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -50,8 +50,14 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract const VALIDATOR_WEBSITE = 'validator_website'; + /** + * @deprecated + * @see VALIDATOR_TIER_PRICE + */ const VALIDATOR_TEAR_PRICE = 'validator_tier_price'; + const VALIDATOR_TIER_PRICE = 'validator_tier_price'; + /** * Validation failure message template definitions. * @@ -221,7 +227,7 @@ public function __construct( $this->_catalogProductEntity = $this->_resourceFactory->create()->getTable('catalog_product_entity'); $this->_oldSkus = $this->retrieveOldSkus(); $this->_validators[self::VALIDATOR_WEBSITE] = $websiteValidator; - $this->_validators[self::VALIDATOR_TEAR_PRICE] = $tierPriceValidator; + $this->_validators[self::VALIDATOR_TIER_PRICE] = $tierPriceValidator; $this->errorAggregator = $errorAggregator; foreach (array_merge($this->errorMessageTemplates, $this->_messageTemplates) as $errorCode => $message) { @@ -536,7 +542,7 @@ protected function getWebSiteId($websiteCode) */ protected function getCustomerGroupId($customerGroup) { - $customerGroups = $this->_getValidator(self::VALIDATOR_TEAR_PRICE)->getCustomerGroups(); + $customerGroups = $this->_getValidator(self::VALIDATOR_TIER_PRICE)->getCustomerGroups(); return $customerGroup == self::VALUE_ALL_GROUPS ? 0 : $customerGroups[$customerGroup]; } From 95b877c866cef8e43a536b0756b13c605d946f4b Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sat, 17 Aug 2019 21:57:07 +0700 Subject: [PATCH 257/841] Resolve Comment "Warning!" in "Allow Symlinks" should have red color like other notices (issue 24162) --- app/code/Magento/Backend/etc/adminhtml/system.xml | 4 +++- app/code/Magento/Backend/i18n/en_US.csv | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index c762dbf58de62..2972ad9019fbc 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -145,7 +145,9 @@ <field id="allow_symlink" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Allow Symlinks</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - <comment>Warning! Enabling this feature is not recommended on production environments because it represents a potential security risk.</comment> + <comment> + <![CDATA[<strong style="color:red">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.]]> + </comment> </field> <field id="minify_html" translate="label comment" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Minify Html</label> diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index bfedd56b14313..daeb597de0c7c 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -333,7 +333,7 @@ Debug,Debug "Add Block Names to Hints","Add Block Names to Hints" "Template Settings","Template Settings" "Allow Symlinks","Allow Symlinks" -"Warning! Enabling this feature is not recommended on production environments because it represents a potential security risk.","Warning! Enabling this feature is not recommended on production environments because it represents a potential security risk." +"<strong style="color:red">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.","<strong style="color:red">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk." "Minify Html","Minify Html" "Minification is not applied in developer mode.","Minification is not applied in developer mode." "Translate Inline","Translate Inline" From 5164240b200d05ed496cd8011f5ab7d2907e4fc6 Mon Sep 17 00:00:00 2001 From: Nikita Chubukov <nikita_chubukov@epam.com> Date: Sat, 17 Aug 2019 21:31:39 +0300 Subject: [PATCH 258/841] MC-15256: Exported customer without modification can not be imported - Fix static test --- app/code/Magento/CustomerImportExport/Model/Import/Customer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php index 914b4fd2a3ca9..563bd2cd7f2b9 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -521,8 +521,10 @@ protected function _importData() ); } elseif ($this->getBehavior($rowData) == \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE) { $processedData = $this->_prepareDataForUpdate($rowData); + // phpcs:disable Magento2.Performance.ForeachArrayMerge $entitiesToCreate = array_merge($entitiesToCreate, $processedData[self::ENTITIES_TO_CREATE_KEY]); $entitiesToUpdate = array_merge($entitiesToUpdate, $processedData[self::ENTITIES_TO_UPDATE_KEY]); + // phpcs:enable foreach ($processedData[self::ATTRIBUTES_TO_SAVE_KEY] as $tableName => $customerAttributes) { if (!isset($attributesToSave[$tableName])) { $attributesToSave[$tableName] = []; From e8e8882b665f0905cbdeed621bf06049436c9c9e Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sun, 18 Aug 2019 22:10:43 +0700 Subject: [PATCH 259/841] Translate csv issue 24162 --- app/code/Magento/Backend/etc/adminhtml/system.xml | 2 +- app/code/Magento/Backend/i18n/en_US.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 2972ad9019fbc..3fc4b69a7d3fe 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -146,7 +146,7 @@ <label>Allow Symlinks</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment> - <![CDATA[<strong style="color:red">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.]]> + <![CDATA[<strong style='color:red'>Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.]]> </comment> </field> <field id="minify_html" translate="label comment" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index daeb597de0c7c..a6876a0dee4db 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -333,7 +333,7 @@ Debug,Debug "Add Block Names to Hints","Add Block Names to Hints" "Template Settings","Template Settings" "Allow Symlinks","Allow Symlinks" -"<strong style="color:red">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.","<strong style="color:red">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk." +"<strong style='color:red'>Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.","<strong style='color:red'>Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk." "Minify Html","Minify Html" "Minification is not applied in developer mode.","Minification is not applied in developer mode." "Translate Inline","Translate Inline" From d334b5e0eab3b86817e6dc973ee9e902587cd49d Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sun, 18 Aug 2019 23:10:26 +0700 Subject: [PATCH 260/841] session lifetime js validate --- app/code/Magento/Backend/etc/adminhtml/system.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index c762dbf58de62..794fb1eabb7cb 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -435,7 +435,7 @@ <label>Admin Session Lifetime (seconds)</label> <comment>Please enter at least 60 and at most 31536000 (one year).</comment> <backend_model>Magento\Backend\Model\Config\SessionLifetime\BackendModel</backend_model> - <validate>validate-digits</validate> + <validate>validate-digits validate-range range-60-31536000</validate> </field> </group> <group id="dashboard" translate="label" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> From 4147b01db0e95e49e7238c2ed61b93649b291ab9 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 18 Aug 2019 23:50:24 +0100 Subject: [PATCH 261/841] Update AdvancedSearch ReadMe --- app/code/Magento/AdvancedSearch/README.md | 40 ++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/AdvancedSearch/README.md b/app/code/Magento/AdvancedSearch/README.md index 763d1917a8546..16914d3f6eade 100644 --- a/app/code/Magento/AdvancedSearch/README.md +++ b/app/code/Magento/AdvancedSearch/README.md @@ -1 +1,39 @@ -AdvancedSearch module introduces advanced search functionality and provides interfaces that allow to implement this functionality by 3rd party search engines +# Magento_AdvancedSearch module +Magento_AdvancedSearch module introduces advanced search functionality and provides interfaces that allow to implement +this functionality by 3rd party search engines. + +## Installation details + +Before disabling or uninstalling this module, note that the following modules depends on this module: +- Magento_Elasticsearch +- Magento_Elasticsearch6 + +For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html). + +## Extensibility + +Extension developers can interact with the Magento_AdvancedSearch module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AdvancedSearch module. + +### Events + +This module observes the following event: + + - `catalogsearch_query_save_after` in the `Magento\AdvancedSearch\Model\Recommendations\SaveSearchQueryRelationsObserver` file. + +For information about an event in Magento 2, see [Events and observers](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). + +### Layouts + +The module interact with the following layout handles in the `view/adminhtml/layout` directory: + +- `catalog_search_block` +- `catalog_search_edit` +- `catalog_search_relatedgrid` + +The module interact with the following layout handles in the `view/frontend/layout` directory: + +- `catalogsearch_result_index` + +For more information about layouts in Magento 2, see the [Layout documentation](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/layout-overview.html). \ No newline at end of file From bfaebc7bafbe5f2bb95dd2f77d464607d0b520ad Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 19 Aug 2019 07:31:39 +0700 Subject: [PATCH 262/841] validate digit range lifetime session --- app/code/Magento/Backend/etc/adminhtml/system.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 794fb1eabb7cb..27d0eb138db1e 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -435,7 +435,7 @@ <label>Admin Session Lifetime (seconds)</label> <comment>Please enter at least 60 and at most 31536000 (one year).</comment> <backend_model>Magento\Backend\Model\Config\SessionLifetime\BackendModel</backend_model> - <validate>validate-digits validate-range range-60-31536000</validate> + <validate>validate-digits validate-digits-range digits-range-60-31536000</validate> </field> </group> <group id="dashboard" translate="label" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> From 3d1b0c1bba0c1054cadfa102583943a5e1823039 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 19 Aug 2019 07:37:37 +0700 Subject: [PATCH 263/841] use double quote in translate csv issue 24162 --- app/code/Magento/Backend/etc/adminhtml/system.xml | 2 +- app/code/Magento/Backend/i18n/en_US.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 3fc4b69a7d3fe..2972ad9019fbc 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -146,7 +146,7 @@ <label>Allow Symlinks</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> <comment> - <![CDATA[<strong style='color:red'>Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.]]> + <![CDATA[<strong style="color:red">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.]]> </comment> </field> <field id="minify_html" translate="label comment" type="select" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index a6876a0dee4db..ea06e06275f8f 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -333,7 +333,7 @@ Debug,Debug "Add Block Names to Hints","Add Block Names to Hints" "Template Settings","Template Settings" "Allow Symlinks","Allow Symlinks" -"<strong style='color:red'>Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.","<strong style='color:red'>Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk." +"<strong style=""color:red"">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk.","<strong style=""color:red"">Warning!</strong> Enabling this feature is not recommended on production environments because it represents a potential security risk." "Minify Html","Minify Html" "Minification is not applied in developer mode.","Minification is not applied in developer mode." "Translate Inline","Translate Inline" From 1b43511aea612f36b14322b3ea4a4654fffc7de6 Mon Sep 17 00:00:00 2001 From: Thomas Klein <thomas@bird.eu> Date: Mon, 22 Jul 2019 15:16:32 +0200 Subject: [PATCH 264/841] standardize the array structure in getAttributes result --- .../Magento/Eav/Model/AttributeProvider.php | 4 ++- .../Framework/Model/EntitySnapshot.php | 3 ++- .../EntitySnapshot/AttributeProvider.php | 27 +++++++++---------- .../AttributeProviderInterface.php | 5 +++- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/Eav/Model/AttributeProvider.php b/app/code/Magento/Eav/Model/AttributeProvider.php index 7f9d0620b6096..419a33664e0eb 100644 --- a/app/code/Magento/Eav/Model/AttributeProvider.php +++ b/app/code/Magento/Eav/Model/AttributeProvider.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Eav\Model; @@ -52,7 +53,7 @@ public function __construct( * Returns array of fields * * @param string $entityType - * @return array + * @return string[] * @throws \Exception */ public function getAttributes($entityType) @@ -66,6 +67,7 @@ public function getAttributes($entityType) foreach ($searchResult->getItems() as $attribute) { $attributes[] = $attribute->getAttributeCode(); } + return $attributes; } } diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot.php b/lib/internal/Magento/Framework/Model/EntitySnapshot.php index 99f0c7f4ed42d..8062bf352ced1 100644 --- a/lib/internal/Magento/Framework/Model/EntitySnapshot.php +++ b/lib/internal/Magento/Framework/Model/EntitySnapshot.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework\Model; @@ -55,7 +56,7 @@ public function registerSnapshot($entityType, $entity) $entityData = $hydrator->extract($entity); $attributes = $this->attributeProvider->getAttributes($entityType); $this->snapshotData[$entityType][$entityData[$metadata->getIdentifierField()]] - = array_intersect_key($entityData, $attributes); + = array_intersect(\array_keys($entityData), $attributes); } /** diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php index beb0b2784f5fd..6b7fcd131ba8b 100644 --- a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php +++ b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework\Model\EntitySnapshot; @@ -53,31 +54,29 @@ public function __construct( * Returns array of fields * * @param string $entityType - * @return array + * @return string[] * @throws \Exception */ public function getAttributes($entityType) { if (!isset($this->registry[$entityType])) { $metadata = $this->metadataPool->getMetadata($entityType); - $this->registry[$entityType] = $metadata->getEntityConnection()->describeTable($metadata->getEntityTable()); - if ($metadata->getLinkField() != $metadata->getIdentifierField()) { - unset($this->registry[$entityType][$metadata->getLinkField()]); - } - $providers = []; - if (isset($this->providers[$entityType])) { - $providers = $this->providers[$entityType]; - } elseif (isset($this->providers['default'])) { - $providers = $this->providers['default']; + $entityDescription = $metadata->getEntityConnection()->describeTable($metadata->getEntityTable()); + if ($metadata->getLinkField() !== $metadata->getIdentifierField()) { + unset($entityDescription[$metadata->getLinkField()]); } + $attributes = []; + $attributes[] = \array_keys($entityDescription); + + $providers = $this->providers[$entityType] ?? $this->providers['default'] ?? []; foreach ($providers as $providerClass) { $provider = $this->objectManager->get($providerClass); - $this->registry[$entityType] = array_merge( - $this->registry[$entityType], - $provider->getAttributes($entityType) - ); + $attributes[] = $provider->getAttributes($entityType); } + + $this->registry[$entityType] = \array_merge(...$attributes); } + return $this->registry[$entityType]; } } diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php index f71f9e591630f..ad09e2ef5a479 100644 --- a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php +++ b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework\Model\EntitySnapshot; @@ -12,8 +13,10 @@ interface AttributeProviderInterface { /** + * Returns array of fields + * * @param string $entityType - * @return array + * @return string[] */ public function getAttributes($entityType); } From eec9c26a44f3a62ef426f9ac326ff36269a77d7b Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 19 Aug 2019 08:21:01 +0000 Subject: [PATCH 265/841] MC-19407: Number of the rows increase very fast in changelog table --- lib/internal/Magento/Framework/Mview/View/Subscription.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Mview/View/Subscription.php b/lib/internal/Magento/Framework/Mview/View/Subscription.php index 67dff1a2cc5db..ddfa39f0a089f 100644 --- a/lib/internal/Magento/Framework/Mview/View/Subscription.php +++ b/lib/internal/Magento/Framework/Mview/View/Subscription.php @@ -214,7 +214,7 @@ protected function buildStatement($event, $changelog) $columns = []; foreach ($columnNames as $columnName) { $columns[] = sprintf( - 'NEW.%1$s <=> OLD.%1$s', + 'NOT(NEW.%1$s <=> OLD.%1$s)', $this->connection->quoteIdentifier($columnName) ); } From 0fb32dc17d4b74dba303bc96d139e20fac605ff4 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Mon, 19 Aug 2019 11:41:37 +0300 Subject: [PATCH 266/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" - Update automated test script --- .../Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml | 2 +- .../Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml index ddfdd1e327a5c..f6f2fc05c524d 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml @@ -28,7 +28,7 @@ <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForPageLoad2"/> </actionGroup> - <actionGroup name="enablePayPalSolution" extends="EnablePayPalConfiguration"> + <actionGroup name="EnablePayPalSolution" extends="EnablePayPalConfiguration"> <remove keyForRemoval="waitForOtherPayPalPaymentsSection"/> <remove keyForRemoval="clickOtherPayPalPaymentsSection"/> <remove keyForRemoval="seeAlertMessage"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml index 2440b916fc018..58904b1de796e 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml @@ -41,13 +41,13 @@ <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> <conditionalClick selector="{{OtherPayPalPaymentsConfigSection.expandTab('us')}}" dependentSelector="{{OtherPayPalPaymentsConfigSection.expandedTab('us')}}" visible="false" stepKey="clickOtherPayPalPaymentsSection"/> - <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckout"> + <actionGroup ref="EnablePayPalSolution" stepKey="enableExpressCheckout"> <argument name="payPalConfigType" value="WPSExpressConfigSection"/> <argument name="countryCode" value="us"/> </actionGroup> <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForPageLoad2"/> - <actionGroup ref="enablePayPalSolution" stepKey="enableExpressCheckout2"> + <actionGroup ref="EnablePayPalSolution" stepKey="enableExpressCheckout2"> <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> <argument name="countryCode" value="us"/> </actionGroup> From 91df9ce068627fe985c2d6335e887fd158d79e50 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 19 Aug 2019 16:08:40 +0700 Subject: [PATCH 267/841] Resolve undefine variable in _createUserRole function --- .../Magento/User/Model/ResourceModel/User.php | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/User/Model/ResourceModel/User.php b/app/code/Magento/User/Model/ResourceModel/User.php index b3e4936266a3f..eb333f4061469 100644 --- a/app/code/Magento/User/Model/ResourceModel/User.php +++ b/app/code/Magento/User/Model/ResourceModel/User.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\User\Model\ResourceModel; use Magento\Authorization\Model\Acl\Role\Group as RoleGroup; @@ -208,28 +210,27 @@ protected function _createUserRole($parentId, ModelUser $user) if ($parentId > 0) { /** @var \Magento\Authorization\Model\Role $parentRole */ $parentRole = $this->_roleFactory->create()->load($parentId); + if ($parentRole->getId()) { + $data = new \Magento\Framework\DataObject( + [ + 'parent_id' => $parentRole->getId(), + 'tree_level' => $parentRole->getTreeLevel() + 1, + 'sort_order' => 0, + 'role_type' => RoleUser::ROLE_TYPE, + 'user_id' => $user->getId(), + 'user_type' => UserContextInterface::USER_TYPE_ADMIN, + 'role_name' => $user->getFirstName(), + ] + ); + + $insertData = $this->_prepareDataForTable($data, $this->getTable('authorization_role')); + $this->getConnection()->insert($this->getTable('authorization_role'), $insertData); + $this->aclDataCache->clean(); + } } else { $role = new \Magento\Framework\DataObject(); $role->setTreeLevel(0); } - - if ($parentRole->getId()) { - $data = new \Magento\Framework\DataObject( - [ - 'parent_id' => $parentRole->getId(), - 'tree_level' => $parentRole->getTreeLevel() + 1, - 'sort_order' => 0, - 'role_type' => RoleUser::ROLE_TYPE, - 'user_id' => $user->getId(), - 'user_type' => UserContextInterface::USER_TYPE_ADMIN, - 'role_name' => $user->getFirstName(), - ] - ); - - $insertData = $this->_prepareDataForTable($data, $this->getTable('authorization_role')); - $this->getConnection()->insert($this->getTable('authorization_role'), $insertData); - $this->aclDataCache->clean(); - } } /** From 7a8026d466c72439447ce225ba59bc26e1a69105 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 19 Aug 2019 16:17:54 +0700 Subject: [PATCH 268/841] remove useless logic --- app/code/Magento/User/Model/ResourceModel/User.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/code/Magento/User/Model/ResourceModel/User.php b/app/code/Magento/User/Model/ResourceModel/User.php index eb333f4061469..b62a41c1a9ccf 100644 --- a/app/code/Magento/User/Model/ResourceModel/User.php +++ b/app/code/Magento/User/Model/ResourceModel/User.php @@ -227,9 +227,6 @@ protected function _createUserRole($parentId, ModelUser $user) $this->getConnection()->insert($this->getTable('authorization_role'), $insertData); $this->aclDataCache->clean(); } - } else { - $role = new \Magento\Framework\DataObject(); - $role->setTreeLevel(0); } } From 723497286f0a0c70c313adccb0bf70869bb2122c Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 19 Aug 2019 13:39:12 +0300 Subject: [PATCH 269/841] =?UTF-8?q?MC-18071:=20Reviews=20not=20displaying?= =?UTF-8?q?=20on=20product=20page=20with=20"Product=20=E2=80=93=20Full=20W?= =?UTF-8?q?idth"=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Review/view/frontend/templates/product/view/list.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml b/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml index ed8ff0595a896..d00c310069573 100644 --- a/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml +++ b/app/code/Magento/Review/view/frontend/templates/product/view/list.phtml @@ -11,7 +11,7 @@ $format = $block->getDateFormat() ?: \IntlDateFormatter::SHORT; ?> <?php if (count($_items)) : ?> <div class="block review-list" id="customer-reviews"> - <?php if (!$block->getHideTitle()): ?> + <?php if (!$block->getHideTitle()) : ?> <div class="block-title"> <strong><?= $block->escapeHtml(__('Customer Reviews')) ?></strong> </div> From d1f7f0d2cc90a62c8768d674708980625b187290 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 19 Aug 2019 17:41:29 +0700 Subject: [PATCH 270/841] Fix static test --- app/code/Magento/User/Model/ResourceModel/User.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/User/Model/ResourceModel/User.php b/app/code/Magento/User/Model/ResourceModel/User.php index b62a41c1a9ccf..4eaf6116056dd 100644 --- a/app/code/Magento/User/Model/ResourceModel/User.php +++ b/app/code/Magento/User/Model/ResourceModel/User.php @@ -265,8 +265,6 @@ public function delete(\Magento\Framework\Model\AbstractModel $user) ['user_id = ?' => $uid, 'user_type = ?' => UserContextInterface::USER_TYPE_ADMIN] ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - throw $e; - } catch (\Exception $e) { $connection->rollBack(); return false; } @@ -474,7 +472,7 @@ public function updateRoleUsersAcl(\Magento\Authorization\Model\Role $role) $users = $role->getRoleUsers(); $rowsCount = 0; - if (sizeof($users) > 0) { + if (count($users) > 0) { $bind = ['reload_acl_flag' => 1]; $where = ['user_id IN(?)' => $users]; $rowsCount = $connection->update($this->getTable('admin_user'), $bind, $where); @@ -616,6 +614,7 @@ public function trackPassword($user, $passwordHash, $lifetime = 0) /** * Get latest password for specified user id + * * Possible false positive when password was changed several times with different lifetime configuration * * @param int $userId From 8a87635c053f545e8d65b3fdc0c55c7f6c9d7a61 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Mon, 19 Aug 2019 13:52:56 +0300 Subject: [PATCH 271/841] magento/magento2#23085: Static test fix. --- app/code/Magento/CustomerImportExport/Model/Import/Address.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/CustomerImportExport/Model/Import/Address.php b/app/code/Magento/CustomerImportExport/Model/Import/Address.php index 7c6dfe12362cd..1a8859d5bd7bf 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Address.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Address.php @@ -761,6 +761,7 @@ protected function _saveCustomerDefaults(array $defaults) { foreach ($defaults as $tableName => $data) { foreach ($data as $customerId => $defaultsData) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $data = array_merge( ['entity_id' => $customerId], $defaultsData @@ -801,11 +802,13 @@ public function getEntityTypeCode() * * @static * @return array + * phpcs:disable Magento2.Functions.StaticFunction */ public static function getDefaultAddressAttributeMapping() { return self::$_defaultAddressAttributeMapping; } + // phpcs:enable /** * Check if address for import is empty (for customer composite mode) From 9c856a56a7c4599a9e377251ac30219076e7b5cf Mon Sep 17 00:00:00 2001 From: Evgeny Petrov <evgeny_petrov@epam.com> Date: Mon, 19 Aug 2019 14:02:30 +0300 Subject: [PATCH 272/841] MC-15341: Default product numbers to display results in poor display on Desktop --- ...ml => StorefrontCheckDefaultNumberProductsToDisplayTest.xml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename app/code/Magento/Catalog/Test/Mftf/Test/{CheckDefaultNumberProductsToDisplayTest.xml => StorefrontCheckDefaultNumberProductsToDisplayTest.xml} (99%) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml similarity index 99% rename from app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml index 5718844fb6df3..1ce856831ceaa 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckDefaultNumberProductsToDisplayTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CheckDefaultNumbersProductsToDisplayTest"> + <test name="StorefrontCheckDefaultNumbersProductsToDisplayTest"> <annotations> <features value="Catalog"/> <title value="Check default numbers: products to display"/> From 72934240c56806d401789e921e62464273cba61d Mon Sep 17 00:00:00 2001 From: roettigl <l.roettig@techdivision.com> Date: Mon, 19 Aug 2019 13:58:32 +0200 Subject: [PATCH 273/841] MC-18221: Clean up throw and catch ignores --- .../Controller/Adminhtml/Index/Rollback.php | 2 +- .../Catalog/Model/ProductRepository.php | 1 - .../Customer/Model/AccountManagement.php | 4 ---- .../Magento/Email/Model/Template/Filter.php | 1 - .../Paypal/Model/Payflow/Transparent.php | 15 +++++++-------- .../Magento/Quote/Model/QuoteManagement.php | 3 +-- .../Magento/Sales/Model/AdminOrder/Create.php | 2 -- .../Magento/Sales/Model/Order/Payment.php | 3 +-- .../System/Design/Theme/UploadJs.php | 2 +- app/code/Magento/User/Model/User.php | 5 ++--- .../Model/AsyncScheduleMultiStoreTest.php | 19 +++++++++---------- .../CurlTransport/BackendDecorator.php | 2 -- .../Framework/Crontab/CrontabManager.php | 1 - lib/internal/Magento/Framework/Escaper.php | 1 - .../Webapi/ServiceInputProcessor.php | 4 +--- .../Command/ModuleConfigStatusCommand.php | 2 +- 16 files changed, 24 insertions(+), 43 deletions(-) diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php index 7f450e7e313cc..f4037476f5e7b 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php @@ -127,7 +127,7 @@ public function execute() $adminSession->destroy(); $response->setRedirectUrl($this->getUrl('*')); - // phpcs:disable Magento2.Exceptions.ThrowCatch + // phpcs:disable Magento2.Exceptions.ThrowCatch - Required Refactoring see MC-19410 } catch (\Magento\Framework\Backup\Exception\CantLoadSnapshot $e) { $errorMsg = __('We can\'t find the backup file.'); } catch (\Magento\Framework\Backup\Exception\FtpConnectionFailed $e) { diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index e961db42d99fe..f415b0b14fa5b 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -845,7 +845,6 @@ private function saveProduct($product): void throw new CouldNotSaveException(__($e->getMessage())); } catch (LocalizedException $e) { throw $e; - // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { throw new CouldNotSaveException( __('The product was unable to be saved. Please try again.'), diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 24899a1c5979c..15e2e17a28ee6 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -579,7 +579,6 @@ public function authenticate($username, $password) } try { $this->getAuthentication()->authenticate($customerId, $password); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (InvalidEmailOrPasswordException $e) { throw new InvalidEmailOrPasswordException(__('Invalid login or password.')); } @@ -890,7 +889,6 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash throw new InputMismatchException( __('A customer with the same email address already exists in an associated website.') ); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (LocalizedException $e) { throw $e; } @@ -910,7 +908,6 @@ public function createAccountWithPasswordHash(CustomerInterface $customer, $hash } } $this->customerRegistry->remove($customer->getId()); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (InputException $e) { $this->customerRepository->delete($customer); throw $e; @@ -1017,7 +1014,6 @@ private function changePasswordForCustomer($customer, $currentPassword, $newPass { try { $this->getAuthentication()->authenticate($customer->getId(), $currentPassword); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (InvalidEmailOrPasswordException $e) { throw new InvalidEmailOrPasswordException( __("The password doesn't match this account. Verify the password and try again.") diff --git a/app/code/Magento/Email/Model/Template/Filter.php b/app/code/Magento/Email/Model/Template/Filter.php index 0e27b2d4c418b..a29b1165d83c8 100644 --- a/app/code/Magento/Email/Model/Template/Filter.php +++ b/app/code/Magento/Email/Model/Template/Filter.php @@ -956,7 +956,6 @@ public function getCssFilesContent(array $files) } } catch (ContentProcessorException $exception) { $css = $exception->getMessage(); - // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\View\Asset\File\NotFoundException $exception) { $css = ''; } diff --git a/app/code/Magento/Paypal/Model/Payflow/Transparent.php b/app/code/Magento/Paypal/Model/Payflow/Transparent.php index f90c8f3792428..f58fc2f78a30a 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Transparent.php +++ b/app/code/Magento/Paypal/Model/Payflow/Transparent.php @@ -7,18 +7,18 @@ namespace Magento\Paypal\Model\Payflow; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\State\InvalidTransitionException; use Magento\Payment\Helper\Formatter; use Magento\Payment\Model\InfoInterface; -use Magento\Paypal\Model\Payflowpro; -use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; -use Magento\Sales\Model\Order\Payment; -use Magento\Paypal\Model\Payflow\Service\Gateway; -use Magento\Framework\Exception\LocalizedException; -use Magento\Payment\Model\Method\TransparentInterface; use Magento\Payment\Model\Method\ConfigInterfaceFactory; -use Magento\Framework\Exception\State\InvalidTransitionException; +use Magento\Payment\Model\Method\TransparentInterface; +use Magento\Paypal\Model\Payflow\Service\Gateway; use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface; use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator; +use Magento\Paypal\Model\Payflowpro; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; +use Magento\Sales\Model\Order\Payment; use Magento\Vault\Api\Data\PaymentTokenInterface; use Magento\Vault\Api\Data\PaymentTokenInterfaceFactory; @@ -195,7 +195,6 @@ public function authorize(InfoInterface $payment, $amount) } catch (LocalizedException $exception) { $payment->setParentTransactionId($response->getData(self::PNREF)); $this->void($payment); - // phpcs:ignore Magento2.Exceptions.ThrowCatch throw new LocalizedException(__("The payment couldn't be processed at this time. Please try again later.")); } diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 5bfbc80452bf9..3dfa89d1fca5a 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -14,9 +14,9 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\StateException; use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Quote\Model\Quote as QuoteEntity; use Magento\Quote\Model\Quote\Address\ToOrder as ToOrderConverter; use Magento\Quote\Model\Quote\Address\ToOrderAddress as ToOrderAddressConverter; -use Magento\Quote\Model\Quote as QuoteEntity; use Magento\Quote\Model\Quote\Item\ToOrderItem as ToOrderItemConverter; use Magento\Quote\Model\Quote\Payment\ToOrderPayment as ToOrderPaymentConverter; use Magento\Sales\Api\Data\OrderInterfaceFactory as OrderFactory; @@ -688,7 +688,6 @@ private function rollbackAddresses( 'exception' => $e, ] ); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $consecutiveException) { $message = sprintf( "An exception occurred on 'sales_model_service_quote_submit_failure' event: %s", diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 01f1fe850b837..dd7438dc48425 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -1134,7 +1134,6 @@ public function updateQuoteItems($items) } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->recollectCart(); throw $e; - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { $this->_logger->critical($e); } @@ -2010,7 +2009,6 @@ protected function _validate() } else { try { $method->validate(); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->_errors[] = $e->getMessage(); } diff --git a/app/code/Magento/Sales/Model/Order/Payment.php b/app/code/Magento/Sales/Model/Order/Payment.php index 5d1d3f0d040a7..23a1ef2e689b7 100644 --- a/app/code/Magento/Sales/Model/Order/Payment.php +++ b/app/code/Magento/Sales/Model/Order/Payment.php @@ -8,13 +8,13 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Sales\Api\CreditmemoManagementInterface as CreditmemoManager; use Magento\Sales\Api\Data\OrderPaymentInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment\Info; use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Sales\Model\Order\Payment\Transaction\ManagerInterface; -use Magento\Sales\Api\CreditmemoManagementInterface as CreditmemoManager; /** * Order payment information @@ -684,7 +684,6 @@ public function refund($creditmemo) $gateway->refund($this, $baseAmountToRefund); $creditmemo->setTransactionId($this->getLastTransId()); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { if (!$captureTxn) { throw new \Magento\Framework\Exception\LocalizedException( diff --git a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php index 89636ad3de50e..a1fcc74546700 100644 --- a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php +++ b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php @@ -52,7 +52,7 @@ public function execute() \Magento\Framework\View\Design\Theme\Customization\File\Js::TYPE ); $result = ['error' => false, 'files' => $customization->generateFileInfo($customJsFiles)]; - // phpcs:disable Magento2.Exceptions.ThrowCatch + // phpcs:disable Magento2.Exceptions.ThrowCatch - Required Refactoring see MC-19410 } catch (\Magento\Framework\Exception\LocalizedException $e) { $result = ['error' => true, 'message' => $e->getMessage()]; } catch (\Exception $e) { diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index dc0aa0cd38343..f059ebfb31a8c 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -7,14 +7,14 @@ namespace Magento\User\Model; use Magento\Backend\Model\Auth\Credential\StorageInterface; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Model\AbstractModel; use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\Model\AbstractModel; use Magento\Framework\Serialize\Serializer\Json; use Magento\User\Api\Data\UserInterface; use Magento\User\Model\Spi\NotificationExceptionInterface; use Magento\User\Model\Spi\NotificatorInterface; -use Magento\Framework\App\DeploymentConfig; /** * Admin user model @@ -921,7 +921,6 @@ public function performIdentityCheck($passwordString) { try { $isCheckSuccessful = $this->verifyIdentity($passwordString); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\AuthenticationException $e) { $isCheckSuccessful = false; } diff --git a/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php index a58bb6b14d069..e52207b807fc5 100644 --- a/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php +++ b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php @@ -9,20 +9,20 @@ namespace Magento\WebapiAsync\Model; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\TestFramework\MessageQueue\PreconditionFailedException; -use Magento\TestFramework\MessageQueue\PublisherConsumerController; -use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException; -use Magento\TestFramework\TestCase\WebapiAbstract; -use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\Data\ProductInterface as Product; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Phrase; use Magento\Framework\Registry; use Magento\Framework\Webapi\Exception; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Catalog\Api\Data\ProductInterface as Product; -use Magento\Framework\ObjectManagerInterface; -use Magento\Store\Model\Store; use Magento\Framework\Webapi\Rest\Request; +use Magento\Store\Model\Store; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException; +use Magento\TestFramework\MessageQueue\PreconditionFailedException; +use Magento\TestFramework\MessageQueue\PublisherConsumerController; +use Magento\TestFramework\TestCase\WebapiAbstract; /** * Check async request for multistore product creation service, scheduling bulk @@ -243,7 +243,6 @@ private function clearProducts() foreach ($this->skus as $sku) { $this->productRepository->deleteById($sku); } - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { throw $e; //nothing to delete diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php index a9a082e2c0027..5b561708be12e 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php @@ -8,7 +8,6 @@ use Magento\Mtf\Config\DataInterface; use Magento\Mtf\Util\Protocol\CurlInterface; -use Magento\Mtf\Util\Protocol\CurlTransport; /** * Curl transport on backend. @@ -109,7 +108,6 @@ protected function authorize() $isAuthorized = true; $_ENV['app_backend_url'] = $url; break; - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { continue; } diff --git a/lib/internal/Magento/Framework/Crontab/CrontabManager.php b/lib/internal/Magento/Framework/Crontab/CrontabManager.php index da81540faf477..fb3537ca0648f 100644 --- a/lib/internal/Magento/Framework/Crontab/CrontabManager.php +++ b/lib/internal/Magento/Framework/Crontab/CrontabManager.php @@ -207,7 +207,6 @@ private function save($content) try { $this->shell->execute('echo "' . $content . '" | crontab -'); - // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (LocalizedException $e) { throw new LocalizedException( new Phrase('Error during saving of crontab: %1', [$e->getPrevious()->getMessage()]), diff --git a/lib/internal/Magento/Framework/Escaper.php b/lib/internal/Magento/Framework/Escaper.php index 364c8bf766117..68a18964d3428 100644 --- a/lib/internal/Magento/Framework/Escaper.php +++ b/lib/internal/Magento/Framework/Escaper.php @@ -89,7 +89,6 @@ function ($errorNumber, $errorString) { $domDocument->loadHTML( '<html><body id="' . $wrapperElementId . '">' . $string . '</body></html>' ); - // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { restore_error_handler(); $this->getLogger()->critical($e); diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index c253a400bed93..5a014380bc0e6 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -19,8 +19,8 @@ use Magento\Framework\Phrase; use Magento\Framework\Reflection\MethodsMap; use Magento\Framework\Reflection\TypeProcessor; -use Magento\Framework\Webapi\Exception as WebapiException; use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; +use Magento\Framework\Webapi\Exception as WebapiException; use Zend\Code\Reflection\ClassReflection; /** @@ -275,7 +275,6 @@ protected function _createFromArray($className, $data) } else { $setterValue = $this->convertValue($value, $returnType); } - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (SerializationException $e) { throw new SerializationException( new Phrase( @@ -324,7 +323,6 @@ protected function convertCustomAttributeValue($customAttributesValueArray, $dat ) { try { $attributeValue = $this->convertValue($customAttributeValue, $type); - // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (SerializationException $e) { throw new SerializationException( new Phrase( diff --git a/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php b/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php index 70dbf14728bd7..ebdef97fb6db4 100644 --- a/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php +++ b/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php @@ -97,7 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln( '<info>The modules configuration is up to date.</info>' ); - // phpcs:disable Magento2.Exceptions.ThrowCatch + // phpcs:disable Magento2.Exceptions.ThrowCatch - Required Refactoring see MC-19410 } catch (\Exception $e) { $output->writeln('<error>' . $e->getMessage() . '</error>'); From 2a65994d17d980e003eaf5f14972d14ff4f37b94 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Mon, 19 Aug 2019 09:15:30 -0500 Subject: [PATCH 274/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../UseCase/WaitAndNotWaitMessagesTest.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php new file mode 100644 index 0000000000000..669df01d2c9e3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\MessageQueue\UseCase; + +use Magento\Framework\App\DeploymentConfig\FileReader; +use Magento\Framework\Config\File\ConfigFilePool; + +class WaitAndNotWaitMessagesTest extends QueueTestCaseAbstract +{ + /** + * @var FileReader + */ + private $reader; + + /** + * @var array + */ + private $config; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->reader = $this->objectManager->get(FileReader::class); + + $this->config = $this->loadConfig(); + } + + public function testDefaultConfiguration() + { + $this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 1]], $this->config); + } + + /** + * @return array + */ + private function loadConfig(): array + { + return $this->reader->load(ConfigFilePool::APP_ENV); + } +} From 47dd9bdece2a0e0ee99000442d066a2b52b47c57 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 19 Aug 2019 15:10:13 +0000 Subject: [PATCH 275/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProductPrice.php | 81 ++++++++----------- 1 file changed, 32 insertions(+), 49 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php index f8eae4ed3a71b..11ba87730bec1 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php @@ -6,82 +6,72 @@ namespace Magento\CatalogRule\Model\Indexer; +use Magento\Catalog\Model\Product; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\StoreManagerInterface; + /** * Reindex product prices according rule settings. */ class ReindexRuleProductPrice { /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ private $storeManager; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder + * @var RuleProductsSelectBuilder */ private $ruleProductsSelectBuilder; /** - * @var \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator + * @var ProductPriceCalculator */ private $productPriceCalculator; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime + * @var TimezoneInterface */ - private $dateTime; + private $localeDate; /** - * @var \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor + * @var RuleProductPricesPersistor */ private $pricesPersistor; /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface - */ - private $localeDate; - - /** - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param StoreManagerInterface $storeManager * @param RuleProductsSelectBuilder $ruleProductsSelectBuilder * @param ProductPriceCalculator $productPriceCalculator - * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime - * @param \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param TimezoneInterface $localeDate + * @param RuleProductPricesPersistor $pricesPersistor */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder $ruleProductsSelectBuilder, - \Magento\CatalogRule\Model\Indexer\ProductPriceCalculator $productPriceCalculator, - \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, - \Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor $pricesPersistor, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + StoreManagerInterface $storeManager, + RuleProductsSelectBuilder $ruleProductsSelectBuilder, + ProductPriceCalculator $productPriceCalculator, + TimezoneInterface $localeDate, + RuleProductPricesPersistor $pricesPersistor ) { $this->storeManager = $storeManager; $this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder; $this->productPriceCalculator = $productPriceCalculator; - $this->dateTime = $dateTime; - $this->pricesPersistor = $pricesPersistor; $this->localeDate = $localeDate; + $this->pricesPersistor = $pricesPersistor; } /** * Reindex product prices. * * @param int $batchCount - * @param \Magento\Catalog\Model\Product|null $product + * @param Product|null $product * @param bool $useAdditionalTable * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function execute( - $batchCount, - \Magento\Catalog\Model\Product $product = null, - $useAdditionalTable = false - ) { - $fromDate = mktime(0, 0, 0, date('m'), date('d') - 1); - $toDate = mktime(0, 0, 0, date('m'), date('d') + 1); - + public function execute($batchCount, Product $product = null, $useAdditionalTable = false) + { /** * Update products rules prices per each website separately * because for each website date in website's timezone should be used @@ -91,8 +81,13 @@ public function execute( $dayPrices = []; $stopFlags = []; $prevKey = null; + $storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId()); - $storeId = $storeGroup->getDefaultStoreId(); + $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true); + $previousDate = (clone $currentDate)->modify('-1 day'); + $previousDate->setTime(23, 59, 59); + $nextDate = (clone $currentDate)->modify('+1 day'); + $nextDate->setTime(0, 0, 0); while ($ruleData = $productsStmt->fetch()) { $ruleProductId = $ruleData['product_id']; @@ -110,24 +105,24 @@ public function execute( } } - $ruleData['from_time'] = $this->roundTime($ruleData['from_time']); - $ruleData['to_time'] = $this->roundTime($ruleData['to_time']); /** * Build prices for each day */ - for ($time = $fromDate; $time <= $toDate; $time += IndexBuilder::SECONDS_IN_DAY) { + foreach ([$previousDate, $currentDate, $nextDate] as $date) { + $time = $date->getTimestamp(); if (($ruleData['from_time'] == 0 || $time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 || $time <= $ruleData['to_time']) ) { $priceKey = $time . '_' . $productKey; + if (isset($stopFlags[$priceKey])) { continue; } if (!isset($dayPrices[$priceKey])) { $dayPrices[$priceKey] = [ - 'rule_date' => $this->localeDate->scopeDate($storeId, $time), + 'rule_date' => $date, 'website_id' => $ruleData['website_id'], 'customer_group_id' => $ruleData['customer_group_id'], 'product_id' => $ruleProductId, @@ -163,16 +158,4 @@ public function execute( return true; } - - /** - * @param int $timeStamp - * @return int - */ - private function roundTime($timeStamp) - { - if (is_numeric($timeStamp) && $timeStamp != 0) { - $timeStamp = $this->dateTime->timestamp($this->dateTime->date('Y-m-d 00:00:00', $timeStamp)); - } - return $timeStamp; - } } From 25bb02e9ff1943b3e6a194bea4c943b528d43630 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 19 Aug 2019 11:51:47 -0500 Subject: [PATCH 276/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix existing test --- .../GraphQl/Catalog/ProductSearchTest.php | 95 ++++++------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 286843e9b53e9..709779e88da54 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -99,44 +99,9 @@ public function testFilterLn() */ public function testAdvancedSearchByOneCustomAttribute() { - $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); - $query = <<<QUERY -{ - products(filter:{ - second_test_configurable: {eq: "{$optionValue}"} - } - pageSize: 3 - currentPage: 1 - ) - { - total_count - items - { - name - sku - } - page_info{ - current_page - page_size - total_pages - } - filters{ - name - request_var - filter_items_count - filter_items{ - label - items_count - value_string - __typename - } - - } - - } -} -QUERY; - + $attributeCode = 'second_test_configurable'; + $optionValue = $this->getDefaultAttributeOptionValue($attributeCode); + $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $optionValue); /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); @@ -380,7 +345,7 @@ public function testFilterByCategoryIdAndCustomAttribute() 'items_count'=> 2 ], ]; - $categoryFilterItems = array_map(null, $expectedCategoryFilterItems,$actualCategoryFilterItems); + $categoryFilterItems = array_map(null, $expectedCategoryFilterItems, $actualCategoryFilterItems); //Validate the categories and sub-categories data in the filter layer foreach ($categoryFilterItems as $index => $categoryFilterData) { @@ -398,12 +363,12 @@ public function testFilterByCategoryIdAndCustomAttribute() } } - private function getQueryProductsWithCustomAttribute($optionValue) + private function getQueryProductsWithCustomAttribute($attributeCode, $optionValue) { return <<<QUERY { products(filter:{ - test_configurable: {eq: "{$optionValue}"} + $attributeCode: {eq: "{$optionValue}"} } pageSize: 3 currentPage: 1 @@ -457,10 +422,10 @@ public function testLayeredNavigationForConfigurableProductWithOutOfStockOption( array_shift($options); $firstOption = $options[0]->getValue(); $secondOption = $options[1]->getValue(); - $query = $this->getQueryProductsWithCustomAttribute($firstOption); + $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); $response = $this->graphQlQuery($query); - //Only 1 product will be returned since only one child product with attribute option1 from 1st Configurable product is OOS + //1 product will be returned since only one child product with attribute option1 from 1st Configurable product is OOS $this->assertEquals(1, $response['products']['total_count']); // Custom attribute filter layer data @@ -498,15 +463,13 @@ public function testLayeredNavigationForConfigurableProductWithOutOfStockOption( 'is_in_stock' => 0] ); $productRepository->save($outOfStockChildProduct); - $query = $this->getQueryProductsWithCustomAttribute($firstOption); + $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); $response = $this->graphQlQuery($query); $this->assertEquals(0, $response['products']['total_count']); $this->assertEmpty($response['products']['items']); $this->assertEmpty($response['products']['filters']); - $i = 0; } - /** * Get array with expected data for layered navigation filters * @@ -521,15 +484,32 @@ private function getExpectedFiltersDataSet() $options = $attribute->getOptions(); // Fetching option ID is required for continuous debug as of autoincrement IDs. return [ + [ + 'name' => 'Price', + 'filter_items_count' => 2, + 'request_var' => 'price', + 'filter_items' => [ + [ + 'label' => '*-10', + 'value_string' => '*_10', + 'items_count' => 1, + ], + [ + 'label' => '10-*', + 'value_string' => '10_*', + 'items_count' => 1, + ], + ], + ], [ 'name' => 'Category', 'filter_items_count' => 1, - 'request_var' => 'cat', + 'request_var' => 'category_id', 'filter_items' => [ [ 'label' => 'Category 1', 'value_string' => '333', - 'items_count' => 3, + 'items_count' => 2, ], ], ], @@ -544,24 +524,7 @@ private function getExpectedFiltersDataSet() 'items_count' => 1, ], ], - ], - [ - 'name' => 'Price', - 'filter_items_count' => 2, - 'request_var' => 'price', - 'filter_items' => [ - [ - 'label' => '<span class="price">$0.00</span> - <span class="price">$9.99</span>', - 'value_string' => '-10', - 'items_count' => 1, - ], - [ - 'label' => '<span class="price">$10.00</span> and above', - 'value_string' => '10-', - 'items_count' => 1, - ], - ], - ], + ] ]; } From 0cd00e6e3b1f5c35a7502f45cec1bb8478b51904 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 19 Aug 2019 11:53:45 -0500 Subject: [PATCH 277/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix existing fixture --- .../_files/products_with_layered_navigation_attribute.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php index 48c47c9988d59..7bee46bc2078f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_attribute.php @@ -36,7 +36,7 @@ 'is_unique' => 0, 'is_required' => 0, 'is_searchable' => 1, - 'is_visible_in_advanced_search' => 0, + 'is_visible_in_advanced_search' => 1, 'is_comparable' => 1, 'is_filterable' => 1, 'is_filterable_in_search' => 1, From a2c62681f6046d42f052bc3f02e497f60fb5bdea Mon Sep 17 00:00:00 2001 From: Tomash Khamlai <tomash.khamlai@gmail.com> Date: Mon, 19 Aug 2019 20:03:15 +0300 Subject: [PATCH 278/841] Specify the upper range for Email Text Length Limit field --- app/code/Magento/Wishlist/etc/adminhtml/system.xml | 2 +- app/code/Magento/Wishlist/i18n/en_US.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Wishlist/etc/adminhtml/system.xml b/app/code/Magento/Wishlist/etc/adminhtml/system.xml index 1e26a1195a7fe..e61c07abca993 100644 --- a/app/code/Magento/Wishlist/etc/adminhtml/system.xml +++ b/app/code/Magento/Wishlist/etc/adminhtml/system.xml @@ -29,7 +29,7 @@ </field> <field id="text_limit" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Email Text Length Limit</label> - <comment>255 by default</comment> + <comment>255 by default. Max - 10000</comment> <validate>validate-digits validate-digits-range digits-range-1-10000</validate> </field> </group> diff --git a/app/code/Magento/Wishlist/i18n/en_US.csv b/app/code/Magento/Wishlist/i18n/en_US.csv index a9acce448c80c..7bcbd0751b7e9 100644 --- a/app/code/Magento/Wishlist/i18n/en_US.csv +++ b/app/code/Magento/Wishlist/i18n/en_US.csv @@ -101,7 +101,7 @@ Back,Back "Max Emails Allowed to be Sent","Max Emails Allowed to be Sent" "10 by default. Max - 10000","10 by default. Max - 10000" "Email Text Length Limit","Email Text Length Limit" -"255 by default","255 by default" +"255 by default. Max - 10000","255 by default. Max - 10000" "General Options","General Options" Enabled,Enabled "My Wish List Link","My Wish List Link" From 716b42e32e1b7a37fcbb99e48b0cbf87c4a7c55e Mon Sep 17 00:00:00 2001 From: Andrii Lugovyi <alugovyi@adobe.com> Date: Mon, 19 Aug 2019 14:27:06 -0500 Subject: [PATCH 279/841] fix lost the table prefix --- setup/src/Magento/Setup/Model/Installer.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/setup/src/Magento/Setup/Model/Installer.php b/setup/src/Magento/Setup/Model/Installer.php index 3c2a613417753..7a097e49c6289 100644 --- a/setup/src/Magento/Setup/Model/Installer.php +++ b/setup/src/Magento/Setup/Model/Installer.php @@ -756,9 +756,10 @@ private function setupFlagTable( SchemaSetupInterface $setup, AdapterInterface $connection ) { - if (!$connection->isTableExists($setup->getTable('flag'))) { + $tableName = $setup->getTable('flag'); + if (!$connection->isTableExists($tableName)) { $table = $connection->newTable( - $setup->getTable('flag') + $tableName )->addColumn( 'flag_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, @@ -797,7 +798,7 @@ private function setupFlagTable( ); $connection->createTable($table); } else { - $this->updateColumnType($connection, 'flag', 'flag_data', 'mediumtext'); + $this->updateColumnType($connection, $tableName, 'flag_data', 'mediumtext'); } } From ce722d904df3f931c4dbc27d317ed0b6757b4f29 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Mon, 19 Aug 2019 16:15:31 -0500 Subject: [PATCH 280/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../MessageQueue/Setup/ConfigOptionsList.php | 2 +- .../UseCase/QueueTestCaseAbstract.php | 2 +- .../UseCase/WaitAndNotWaitMessagesTest.php | 113 +++++++++++++++++- 3 files changed, 113 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php index bd81ae633429d..d3e559e14e152 100644 --- a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php +++ b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php @@ -46,7 +46,7 @@ public function getOptions() SelectConfigOption::FRONTEND_WIZARD_SELECT, $this->selectOptions, self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES, - 'Should consumers wait for message from the queue? 1 - Yes, 0 - No', + 'Should consumers wait for a message from the queue? 1 - Yes, 0 - No', self::DEFULT_CONSUMERS_WAIT_FOR_MESSAGES ), ]; diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php index bda232c7fb9c4..ce1177fd20175 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php @@ -45,7 +45,7 @@ class QueueTestCaseAbstract extends \PHPUnit\Framework\TestCase /** * @var PublisherConsumerController */ - private $publisherConsumerController; + protected $publisherConsumerController; protected function setUp() { diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php index 669df01d2c9e3..331ebaed3b89e 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php @@ -6,7 +6,10 @@ namespace Magento\Framework\MessageQueue\UseCase; use Magento\Framework\App\DeploymentConfig\FileReader; +use Magento\TestModuleAsyncAmqp\Model\AsyncTestData; +use Magento\Framework\App\DeploymentConfig\Writer; use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Filesystem; class WaitAndNotWaitMessagesTest extends QueueTestCaseAbstract { @@ -15,25 +18,113 @@ class WaitAndNotWaitMessagesTest extends QueueTestCaseAbstract */ private $reader; + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var ConfigFilePool + */ + private $configFilePool; + /** * @var array */ private $config; + /** + * @var \Magento\TestModuleAsyncAmqp\Model\AsyncTestData + */ + protected $msgObject; + + /** + * {@inheritdoc} + */ + protected $consumers = ['mixed.sync.and.async.queue.consumer']; + + /** + * @var string[] + */ + protected $messages = ['message1', 'message2', 'message3']; + + /** + * @var int|null + */ + protected $maxMessages = 4; + /** * @inheritdoc */ protected function setUp() { parent::setUp(); + $this->msgObject = $this->objectManager->create(AsyncTestData::class); $this->reader = $this->objectManager->get(FileReader::class); - + $this->filesystem = $this->objectManager->get(Filesystem::class); $this->config = $this->loadConfig(); } - public function testDefaultConfiguration() + /** + * Check if consumers wait for messages from the queue + */ + public function testWaitForMessages() { $this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 1]], $this->config); + + foreach ($this->messages as $msg) { + $this->publishMessage($msg); + } + + $this->waitForAsynchronousResult(count($this->messages), $this->logFilePath); + + foreach ($this->messages as $item) { + $this->assertContains($item, file_get_contents($this->logFilePath)); + } + + $this->publishMessage('message4'); + $this->waitForAsynchronousResult(count($this->messages) + 1, $this->logFilePath); + $this->assertContains('message4', file_get_contents($this->logFilePath)); + } + + /** + * Check if consumers do not wait for messages from the queue and die + */ + public function testNotWaitForMessages(): void + { + $this->publisherConsumerController->stopConsumers(); + + $config = $this->config; + $config['queue']['consumers_wait_for_messages'] = 0; + $this->writeConfig($config); + + $this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 0]], $this->loadConfig()); + foreach ($this->messages as $msg) { + $this->publishMessage($msg); + } + + $this->publisherConsumerController->startConsumers(); + $this->waitForAsynchronousResult(count($this->messages), $this->logFilePath); + + foreach ($this->messages as $item) { + $this->assertContains($item, file_get_contents($this->logFilePath)); + } + + // Checks that consumers do not wait 4th message and die + $this->assertArraySubset( + ['mixed.sync.and.async.queue.consumer' => []], + $this->publisherConsumerController->getConsumersProcessIds() + ); + } + + /** + * @param string $msg + */ + private function publishMessage(string $msg): void + { + $this->msgObject->setValue($msg); + $this->msgObject->setTextFilePath($this->logFilePath); + $this->publisher->publish('multi.topic.queue.topic.c', $this->msgObject); } /** @@ -43,4 +134,22 @@ private function loadConfig(): array { return $this->reader->load(ConfigFilePool::APP_ENV); } + + /** + * @param array $config + */ + private function writeConfig(array $config): void + { + $writer = $this->objectManager->get(Writer::class); + $writer->saveConfig([ConfigFilePool::APP_ENV => $config], true); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + parent::tearDown(); + $this->writeConfig($this->config); + } } From 21ea200d96b86dc9261e0217915667c85f7ff6aa Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Mon, 19 Aug 2019 16:30:34 -0500 Subject: [PATCH 281/841] MC-18512: Dynamically inject all searchable custom attributes for product filtering --- .../Model/Config/FilterAttributeReader.php | 30 +++++++++++++++---- .../Model/Config/SortAttributeReader.php | 4 --- .../Magento/CatalogGraphQl/etc/graphql/di.xml | 4 +-- .../CatalogGraphQl/etc/schema.graphqls | 17 ++++++++--- app/code/Magento/GraphQl/etc/schema.graphqls | 15 ++++++++++ 5 files changed, 54 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php index 1dd218329112c..9310dc593d40c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php @@ -30,7 +30,9 @@ class FilterAttributeReader implements ReaderInterface /** * Filter input types */ - private const FILTER_TYPE = 'FilterTypeInput'; + private const FILTER_EQUAL_TYPE = 'FilterEqualTypeInput'; + private const FILTER_RANGE_TYPE = 'FilterRangeTypeInput'; + private const FILTER_LIKE_TYPE = 'FilterLikeTypeInput'; /** * @var MapperInterface @@ -67,16 +69,12 @@ public function read($scope = null) : array $config = []; foreach ($this->getAttributeCollection() as $attribute) { - if (!$attribute->getIsUserDefined()) { - //do not override fields defined in schema.graphqls - continue; - } $attributeCode = $attribute->getAttributeCode(); foreach ($typeNames as $typeName) { $config[$typeName]['fields'][$attributeCode] = [ 'name' => $attributeCode, - 'type' => self::FILTER_TYPE, + 'type' => $this->getFilterType($attribute->getFrontendInput()), 'arguments' => [], 'required' => false, 'description' => sprintf('Attribute label: %s', $attribute->getDefaultFrontendLabel()) @@ -87,6 +85,26 @@ public function read($scope = null) : array return $config; } + /** + * Map attribute type to filter type + * + * @param string $attributeType + * @return string + */ + private function getFilterType($attributeType): string + { + $filterTypeMap = [ + 'price' => self::FILTER_RANGE_TYPE, + 'date' => self::FILTER_RANGE_TYPE, + 'select' => self::FILTER_EQUAL_TYPE, + 'boolean' => self::FILTER_EQUAL_TYPE, + 'text' => self::FILTER_LIKE_TYPE, + 'textarea' => self::FILTER_LIKE_TYPE, + ]; + + return $filterTypeMap[$attributeType] ?? self::FILTER_LIKE_TYPE; + } + /** * Create attribute collection * diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php index 079084e95b9de..215b28be0579c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/SortAttributeReader.php @@ -61,10 +61,6 @@ public function read($scope = null) : array $attributes = $this->attributesCollection->addSearchableAttributeFilter()->addFilter('used_for_sort_by', 1); /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ foreach ($attributes as $attribute) { - if (!$attribute->getIsUserDefined()) { - //do not override fields defined in schema.graphqls - continue; - } $attributeCode = $attribute->getAttributeCode(); $attributeLabel = $attribute->getDefaultFrontendLabel(); foreach ($map as $type) { diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index 3ada365ec5065..ea2704f1a4ee5 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -49,10 +49,10 @@ <item name="checkbox" xsi:type="string">CustomizableCheckboxOption</item> </item> <item name="sort_attributes" xsi:type="array"> - <item name="product_sort_attributes" xsi:type="string">ProductSortInput</item> + <item name="product_sort_attributes" xsi:type="string">ProductAttributeSortInput</item> </item> <item name="filter_attributes" xsi:type="array"> - <item name="product_filter_attributes" xsi:type="string">ProductFilterInput</item> + <item name="product_filter_attributes" xsi:type="string">ProductAttributeFilterInput</item> </item> </argument> </arguments> diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index ea56faf94408e..e6daf4f4d753e 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -4,10 +4,10 @@ type Query { products ( search: String @doc(description: "Performs a full-text search using the specified key words."), - filter: ProductFilterInput @doc(description: "Identifies which product attributes to search for and return."), + filter: ProductAttributeFilterInput @doc(description: "Identifies which product attributes to search for and return."), pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."), currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), - sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") + sort: ProductAttributeSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") ): Products @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") category ( @@ -280,7 +280,11 @@ type CategoryProducts @doc(description: "The category products object returned i total_count: Int @doc(description: "The number of products returned.") } -input ProductFilterInput @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { +input ProductAttributeFilterInput @doc(description: "ProductAttributeFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { + category_id: FilterEqualTypeInput @doc(description: "Filter product by category id") +} + +input ProductFilterInput @deprecated(reason: "Attributes used in this input are hardcoded and some of them are not searcheable. Use @ProductAttributeFilterInput instead") @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { name: FilterTypeInput @doc(description: "The product name. Customers use this name to identify the product.") sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: FilterTypeInput @doc(description: "Detailed information about the product. The value can include simple HTML tags.") @@ -333,7 +337,7 @@ type ProductMediaGalleryEntriesVideoContent @doc(description: "ProductMediaGalle video_metadata: String @doc(description: "Optional data about the video.") } -input ProductSortInput @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") { +input ProductSortInput @deprecated(reason: "Attributes used in this input are hardcoded and some of them are not searcheable. Use @ProductAttributeSortInput instead") @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") { name: SortEnum @doc(description: "The product name. Customers use this name to identify the product.") sku: SortEnum @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: SortEnum @doc(description: "Detailed information about the product. The value can include simple HTML tags.") @@ -367,6 +371,11 @@ input ProductSortInput @doc(description: "ProductSortInput specifies the attribu gift_message_available: SortEnum @doc(description: "Indicates whether a gift message is available.") } +input ProductAttributeSortInput @doc(description: "ProductAttributeSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order. It's possible to sort products using searchable attributes with enabled 'Use in Filter Options' option") +{ + position: SortEnum @doc(description: "The position of products") +} + type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product.") { id: Int @doc(description: "The identifier assigned to the object.") media_type: String @doc(description: "image or video.") diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index eb6a88a4d487d..997572471122f 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -65,6 +65,21 @@ input FilterTypeInput @doc(description: "FilterTypeInput specifies which action nin: [String] @doc(description: "Not in. The value can contain a set of comma-separated values") } +input FilterEqualTypeInput @doc(description: "Specifies which action will be performed in a query ") { + in: [String] + eq: String +} + +input FilterRangeTypeInput @doc(description: "Specifies which action will be performed in a query ") { + from: String + to: String +} + +input FilterLikeTypeInput @doc(description: "Specifies which action will be performed in a query ") { + like: String + eq: String +} + type SearchResultPageInfo @doc(description: "SearchResultPageInfo provides navigation for the query response") { page_size: Int @doc(description: "Specifies the maximum number of items to return") current_page: Int @doc(description: "Specifies which page of results to return") From b7ed0202a82f595f65341e8ce97e0fb037c163d3 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Tue, 20 Aug 2019 00:08:51 +0100 Subject: [PATCH 282/841] Changes requested during PR CR --- app/code/Magento/AdvancedSearch/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/AdvancedSearch/README.md b/app/code/Magento/AdvancedSearch/README.md index 16914d3f6eade..999721fca1c8f 100644 --- a/app/code/Magento/AdvancedSearch/README.md +++ b/app/code/Magento/AdvancedSearch/README.md @@ -1,6 +1,5 @@ # Magento_AdvancedSearch module -Magento_AdvancedSearch module introduces advanced search functionality and provides interfaces that allow to implement -this functionality by 3rd party search engines. +The Magento_AdvancedSearch module introduces advanced search functionality and provides interfaces that allow third-party search engines to implement this functionality. ## Installation details @@ -26,13 +25,13 @@ For information about an event in Magento 2, see [Events and observers](http://d ### Layouts -The module interact with the following layout handles in the `view/adminhtml/layout` directory: +The module interacts with the following layout handles in the `view/adminhtml/layout` directory: - `catalog_search_block` - `catalog_search_edit` - `catalog_search_relatedgrid` -The module interact with the following layout handles in the `view/frontend/layout` directory: +The module interacts with the following layout handles in the `view/frontend/layout` directory: - `catalogsearch_result_index` From 863d19eed2019183131b7cfb52e0c831c1dede48 Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Tue, 20 Aug 2019 08:59:54 +0200 Subject: [PATCH 283/841] MC-18221: Fix static tests --- .../Catalog/Model/ProductRepository.php | 2 +- .../Customer/Model/AccountManagement.php | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index f415b0b14fa5b..d656a0a9ac5b4 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -735,7 +735,7 @@ private function getCollectionProcessor() { if (!$this->collectionProcessor) { $this->collectionProcessor = \Magento\Framework\App\ObjectManager::getInstance()->get( - 'Magento\Catalog\Model\Api\SearchCriteria\ProductCollectionProcessor' + \Magento\Catalog\Model\Api\SearchCriteria\ProductCollectionProcessor::class ); } return $this->collectionProcessor; diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 15e2e17a28ee6..dcf758c1c0f0d 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -66,49 +66,65 @@ class AccountManagement implements AccountManagementInterface { /** - * Configuration paths for email templates and identities + * Configuration paths for create account email template * * @deprecated */ const XML_PATH_REGISTER_EMAIL_TEMPLATE = 'customer/create_account/email_template'; /** + * Configuration paths for register no password email template + * * @deprecated */ const XML_PATH_REGISTER_NO_PASSWORD_EMAIL_TEMPLATE = 'customer/create_account/email_no_password_template'; /** + * Configuration paths for remind email identity + * * @deprecated */ const XML_PATH_REGISTER_EMAIL_IDENTITY = 'customer/create_account/email_identity'; /** + * Configuration paths for remind email template + * * @deprecated */ const XML_PATH_REMIND_EMAIL_TEMPLATE = 'customer/password/remind_email_template'; /** + * Configuration paths for forgot email email template + * * @deprecated */ const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'customer/password/forgot_email_template'; /** + * Configuration paths for forgot email identity + * * @deprecated */ const XML_PATH_FORGOT_EMAIL_IDENTITY = 'customer/password/forgot_email_identity'; /** + * Configuration paths for account confirmation required + * * @deprecated * @see AccountConfirmation::XML_PATH_IS_CONFIRM */ const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm'; /** + * Configuration paths for account confirmation email template + * * @deprecated */ const XML_PATH_CONFIRM_EMAIL_TEMPLATE = 'customer/create_account/email_confirmation_template'; /** + * Configuration paths for confirmation confirmed email template + * * @deprecated */ const XML_PATH_CONFIRMED_EMAIL_TEMPLATE = 'customer/create_account/email_confirmed_template'; From 88ee6cf2824b9b12b46d82cffe6d26045f4ed74f Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 20 Aug 2019 14:53:43 +0700 Subject: [PATCH 284/841] Rss Model Refactor --- app/code/Magento/Review/Model/Rss.php | 33 ++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Review/Model/Rss.php b/app/code/Magento/Review/Model/Rss.php index df8a5dbb96841..876a3722da61e 100644 --- a/app/code/Magento/Review/Model/Rss.php +++ b/app/code/Magento/Review/Model/Rss.php @@ -3,11 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Review\Model; +use Magento\Framework\App\ObjectManager; + /** - * Class Rss - * @package Magento\Catalog\Model\Rss\Product + * Model Rss + * + * Class \Magento\Catalog\Model\Rss\Product\Rss */ class Rss extends \Magento\Framework\Model\AbstractModel { @@ -24,18 +30,39 @@ class Rss extends \Magento\Framework\Model\AbstractModel protected $eventManager; /** + * Rss constructor. + * * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param ReviewFactory $reviewFactory + * @param \Magento\Framework\Model\Context|null $context + * @param \Magento\Framework\Registry|null $registry + * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource + * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection + * @param array $data */ public function __construct( \Magento\Framework\Event\ManagerInterface $eventManager, - \Magento\Review\Model\ReviewFactory $reviewFactory + \Magento\Review\Model\ReviewFactory $reviewFactory, + \Magento\Framework\Model\Context $context = null, + \Magento\Framework\Registry $registry = null, + \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, + \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, + array $data = [] ) { $this->reviewFactory = $reviewFactory; $this->eventManager = $eventManager; + $context = $context ?? ObjectManager::getInstance()->get( + \Magento\Framework\Model\Context::class + ); + $registry = $registry ?? ObjectManager::getInstance()->get( + \Magento\Framework\Registry::class + ); + parent::__construct($context, $registry, $resource, $resourceCollection, $data); } /** + * Get Product Collection + * * @return $this|\Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection */ public function getProductCollection() From 8f6f562dc3e9a1ea9d4dd1340a0ebf4aa84fa444 Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Tue, 20 Aug 2019 10:03:00 +0200 Subject: [PATCH 285/841] MC-18221: Fix static tests --- app/code/Magento/Sales/Model/AdminOrder/Create.php | 1 + app/code/Magento/User/Model/User.php | 6 +++++- .../Setup/Console/Command/ModuleConfigStatusCommand.php | 3 ++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index dd7438dc48425..cf6d55d8c76f3 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -1990,6 +1990,7 @@ protected function _validate() /** @var \Magento\Quote\Model\Quote\Item $item */ $messages = $item->getMessage(false); if ($item->getHasError() && is_array($messages) && !empty($messages)) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $this->_errors = array_merge($this->_errors, $messages); } } diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index f059ebfb31a8c..6294775149567 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -55,7 +55,11 @@ class User extends AbstractModel implements StorageInterface, UserInterface */ const XML_PATH_USER_NOTIFICATION_TEMPLATE = 'admin/emails/user_notification_template'; - /** @deprecated */ + /** + * Configuration paths for admin user reset password email template + * + * @deprecated + */ const XML_PATH_RESET_PASSWORD_TEMPLATE = 'admin/emails/reset_password_template'; const MESSAGE_ID_PASSWORD_EXPIRED = 'magento_user_password_expired'; diff --git a/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php b/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php index ebdef97fb6db4..e191a63ff186c 100644 --- a/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php +++ b/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php @@ -97,7 +97,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln( '<info>The modules configuration is up to date.</info>' ); - // phpcs:disable Magento2.Exceptions.ThrowCatch - Required Refactoring see MC-19410 + // Required Refactoring see MC-19410 + // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { $output->writeln('<error>' . $e->getMessage() . '</error>'); From 0be11cc9db67398006ffed770eab52cea25cfb2b Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 20 Aug 2019 15:24:03 +0700 Subject: [PATCH 286/841] Fix static test rss model --- app/code/Magento/Review/Model/Rss.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Review/Model/Rss.php b/app/code/Magento/Review/Model/Rss.php index 876a3722da61e..f5abdbb4d3c9e 100644 --- a/app/code/Magento/Review/Model/Rss.php +++ b/app/code/Magento/Review/Model/Rss.php @@ -51,12 +51,8 @@ public function __construct( ) { $this->reviewFactory = $reviewFactory; $this->eventManager = $eventManager; - $context = $context ?? ObjectManager::getInstance()->get( - \Magento\Framework\Model\Context::class - ); - $registry = $registry ?? ObjectManager::getInstance()->get( - \Magento\Framework\Registry::class - ); + $context = $context ?? ObjectManager::getInstance()->get(\Magento\Framework\Model\Context::class); + $registry = $registry ?? ObjectManager::getInstance()->get(\Magento\Framework\Registry::class); parent::__construct($context, $registry, $resource, $resourceCollection, $data); } From b8e757f7e06e8c4ff258f4e7eea35668a6136718 Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Tue, 20 Aug 2019 10:30:50 +0200 Subject: [PATCH 287/841] MC-18221: Fix static tests --- app/code/Magento/Customer/Model/AccountManagement.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index dcf758c1c0f0d..7be8699495bf5 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -177,11 +177,15 @@ class AccountManagement implements AccountManagementInterface const XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER = 'customer/password/required_character_classes_number'; /** + * Configuration path to customer reset password email template + * * @deprecated */ const XML_PATH_RESET_PASSWORD_TEMPLATE = 'customer/password/reset_password_template'; /** + * Minimum password length + * * @deprecated */ const MIN_PASSWORD_LENGTH = 6; From 3d55863b67754d553e10c1d5311405f5c9d4b4c1 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Tue, 20 Aug 2019 13:31:48 +0300 Subject: [PATCH 288/841] MAGETWO-44170: Not pass function test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Fix modularity issue --- ...AdminProductTypeSwitchingOnEditingTest.xml | 280 ------------------ ...AdminProductTypeSwitchingOnEditingTest.xml | 243 +++++++++++++++ 2 files changed, 243 insertions(+), 280 deletions(-) create mode 100644 app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index cac5e209af9fd..45f404478809a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -8,195 +8,6 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> - <annotations> - <features value="Catalog"/> - <title value="Simple product type switching on editing to configurable product"/> - <description value="Simple product type switching on editing to configurable product"/> - <testCaseId value="MAGETWO-29633"/> - <useCaseId value="MAGETWO-44170"/> - <severity value="MAJOR"/> - <group value="catalog"/> - </annotations> - <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Create product--> - <comment userInput="Create product" stepKey="commentCreateProduct"/> - <createData entity="SimpleProduct2" stepKey="createProduct"/> - <!--Create attribute with options--> - <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> - <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> - <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - </before> - <after> - <!--Delete product--> - <comment userInput="Delete product" stepKey="commentDeleteProduct"/> - <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> - <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> - <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> - <argument name="product" value="$$createProduct$$"/> - </actionGroup> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> - <!--Add configurations to product--> - <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> - <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> - <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> - <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> - <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> - </actionGroup> - <actionGroup ref="saveConfiguredProduct" stepKey="saveConfigProductForm"/> - <!--Assert configurable product on Admin product page grid--> - <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> - <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> - <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> - <argument name="sku" value="$$createProduct.sku$$"/> - </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> - <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> - <!--Assert configurable product on storefront--> - <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> - <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage"/> - <waitForPageLoad stepKey="homeWaitForPageLoad"/> - <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName1"> - <argument name="phrase" value="$$createProduct.name$$"/> - </actionGroup> - <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeConfProductInSearchResultPage"/> - <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfProductChildOneInSearchResultPage"/> - <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfProductChildTwoInSearchResultPage"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> - <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> - <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> - <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> - <see userInput="option1" stepKey="verifyOption1Exists"/> - <see userInput="option2" stepKey="verifyOption2Exists"/> - </test> - <test name="AdminConfigurableProductTypeSwitchingToVirtualProductTest" extends="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> - <annotations> - <features value="Catalog"/> - <title value="Configurable product type switching on editing to virtual product"/> - <description value="Configurable product type switching on editing to virtual product"/> - <testCaseId value="MC-17952"/> - <useCaseId value="MAGETWO-44170"/> - <severity value="MAJOR"/> - <group value="catalog"/> - </annotations> - <!--Delete product configurations--> - <comment userInput="Delete product configuration" stepKey="commentDeleteConfigs"/> - <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToConfigProductPage"/> - <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> - <conditionalClick selector="{{ AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> - <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption1Actions"/> - <click selector="{{AdminProductFormConfigurationsSection.removeProductBtn}}" stepKey="clickRemoveOption1"/> - <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption2Actions"/> - <click selector="{{AdminProductFormConfigurationsSection.removeProductBtn}}" stepKey="clickRemoveOption2"/> - <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{SimpleProduct2.price}}" stepKey="fillProductPrice"/> - <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{SimpleProduct2.quantity}}" stepKey="fillProductQty"/> - <clearField selector="{{AdminProductFormSection.productWeight}}" stepKey="clearWeightField"/> - <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has no weight" stepKey="selectNoWeight"/> - <actionGroup ref="saveProductForm" stepKey="saveVirtualProductForm"/> - <!--Assert virtual product on Admin product page grid--> - <comment userInput="Assert virtual product on Admin product page grid" stepKey="commentAssertVirtualProductOnAdmin"/> - <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForVirtual"/> - <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuForVirtual"> - <argument name="sku" value="$$createProduct.sku$$"/> - </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeVirtualProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Virtual Product" stepKey="seeVirtualProductTypeInGrid"/> - <!--Assert virtual product on storefront--> - <comment userInput="Assert virtual product on storefront" stepKey="commentAssertVirtualProductOnStorefront"/> - <amOnPage url="{{StorefrontHomePage.url}}" stepKey="openStorefrontPage"/> - <waitForPageLoad stepKey="storefrontWaitForPageLoad"/> - <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByVirtualProductName"> - <argument name="phrase" value="$$createProduct.name$$"/> - </actionGroup> - <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeVirtualProductInSearchResultPage"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openVirtualProductPage"/> - <waitForPageLoad stepKey="waitForStorefrontVirtualProductPageLoad"/> - <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertVirtualProductInStock"/> - </test> - <test name="AdminVirtualProductTypeSwitchingToConfigurableProductTest"> - <annotations> - <features value="Catalog"/> - <title value="Virtual product type switching on editing to configurable product"/> - <description value="Virtual product type switching on editing to configurable product"/> - <testCaseId value="MC-17953"/> - <useCaseId value="MAGETWO-44170"/> - <severity value="MAJOR"/> - <group value="catalog"/> - </annotations> - <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Create product--> - <comment userInput="Create product" stepKey="commentCreateProduct"/> - <createData entity="VirtualProduct" stepKey="createProduct"/> - <!--Create attribute with options--> - <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> - <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> - <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - </before> - <after> - <!--Delete product--> - <comment userInput="Delete product" stepKey="commentDeleteProduct"/> - <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> - <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> - <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> - <argument name="product" value="$$createProduct$$"/> - </actionGroup> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> - <!--Add configurations to product--> - <comment userInput="Add configurations to product" stepKey="commentAddConfigurations"/> - <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToConfigProductPage"/> - <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> - <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForConfigurableProduct"/> - <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurationsForProduct"> - <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> - </actionGroup> - <actionGroup ref="saveConfiguredProduct" stepKey="saveNewConfigurableProductForm"/> - <!--Assert configurable product on Admin product page grid--> - <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigurableProductOnAdmin"/> - <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfigurable"/> - <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuForConfigurable"> - <argument name="sku" value="$$createProduct.sku$$"/> - </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeConfigurableProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeConfigurableProductTypeInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfigurableProductSkuInGrid1"/> - <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfigurableProductSkuInGrid2"/> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearConfigurableProductFilters"/> - <!--Assert configurable product on storefront--> - <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigurableProductOnStorefront"/> - <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontHomePage"/> - <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> - <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByConfigurableProductName"> - <argument name="phrase" value="$$createProduct.name$$"/> - </actionGroup> - <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeConfigurableProductInSearchResultPage"/> - <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfigurableProductChildOneInSearchResultPage"/> - <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfigurableProductChildTwoInSearchResultPage"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openConfigurableProductPage"/> - <waitForPageLoad stepKey="waitForStorefrontConfigurableProductPageLoad"/> - <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertConfigurableProductInStock"/> - <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickConfigurableAttributeDropDown"/> - <see userInput="option1" stepKey="verifyConfigurableProductOption1Exists"/> - <see userInput="option2" stepKey="verifyConfigurableProductOption2Exists"/> - </test> <test name="AdminVirtualProductTypeSwitchingToDownloadableProductTest"> <annotations> <features value="Catalog"/> @@ -241,12 +52,6 @@ <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearDownloadableProductFilters"/> <!--Assert downloadable product on storefront--> <comment userInput="Assert downloadable product on storefront" stepKey="commentAssertDownloadableProductOnStorefront"/> - <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontHomePage"/> - <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> - <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByDownloadableProductName"> - <argument name="phrase" value="$$createProduct.name$$"/> - </actionGroup> - <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductInSearchResultPage"/> <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openDownloadableProductPage"/> <waitForPageLoad stepKey="waitForStorefrontDownloadableProductPageLoad"/> <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertDownloadableProductInStock"/> @@ -281,12 +86,6 @@ <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearSimpleProductFilters"/> <!--Assert simple product on storefront--> <comment userInput="Assert simple product on storefront" stepKey="commentAssertSimpleProductOnStorefront"/> - <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> - <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchBySimpleProductName"> - <argument name="phrase" value="$$createProduct.name$$"/> - </actionGroup> - <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeSimpleProductInSearchResultPage"/> <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openSimpleProductPage"/> <waitForPageLoad stepKey="waitForStorefrontSimpleProductPageLoad"/> <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertSimpleProductInStock"/> @@ -336,89 +135,10 @@ <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearDownloadableProductFilters"/> <!--Assert downloadable product on storefront--> <comment userInput="Assert downloadable product on storefront" stepKey="commentAssertDownloadableProductOnStorefront"/> - <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStoreFrontHomePage"/> - <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> - <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByDownloadableProductName"> - <argument name="phrase" value="$$createProduct.name$$"/> - </actionGroup> - <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductInSearchResultPage"/> <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openDownloadableProductPage"/> <waitForPageLoad stepKey="waitForStorefrontDownloadableProductPageLoad"/> <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertDownloadableProductInStock"/> <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> <seeElement selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}" stepKey="seeDownloadableLink" /> </test> - <test name="AdminDownloadableProductTypeSwitchingToConfigurableProductTest" extends="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> - <annotations> - <features value="Catalog"/> - <title value="Downloadable product type switching on editing to configurable product"/> - <description value="Downloadable product type switching on editing to configurable product"/> - <testCaseId value="MC-17957"/> - <useCaseId value="MAGETWO-44170"/> - <severity value="MAJOR"/> - <group value="catalog"/> - </annotations> - <before> - <!--Create attribute with options--> - <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> - <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> - <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - </before> - <after> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Delete product--> - <comment userInput="Delete product" stepKey="commentDeleteProduct"/> - <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> - <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> - <argument name="product" value="$$createProduct$$"/> - </actionGroup> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> - <!--Add configurations to product--> - <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> - <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> - <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> - <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection"/> - <uncheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> - <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForProduct"/> - <actionGroup ref="saveProductForm" stepKey="saveDownloadssdableProductForm"/> - <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> - <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> - </actionGroup> - <actionGroup ref="saveConfiguredProduct" stepKey="saveConfigProductForm"/> - <!--Assert configurable product on Admin product page grid--> - <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> - <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfig"/> - <click selector="{{AdminProductGridSection.columnHeader('ID')}}" stepKey="clickIdHeaderToSortAsc"/> - <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuFroConfig"> - <argument name="sku" value="$$createProduct.sku$$"/> - </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> - <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> - <!--Assert configurable product on storefront--> - <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> - <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnHomePage"/> - <waitForPageLoad stepKey="homeWaitForPageLoad"/> - <actionGroup ref="StorefrontCheckQuickSearchStringActionGroup" stepKey="quickSearchByProductName1"> - <argument name="phrase" value="$$createProduct.name$$"/> - </actionGroup> - <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$" stepKey="seeConfProductInSearchResultPage"/> - <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfProductChildOneInSearchResultPage"/> - <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfProductChildTwoInSearchResultPage"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> - <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> - <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> - <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> - <see userInput="option1" stepKey="verifyOption1Exists"/> - <see userInput="option2" stepKey="verifyOption2Exists"/> - </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml new file mode 100644 index 0000000000000..071ee8d18920f --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -0,0 +1,243 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Simple product type switching on editing to configurable product"/> + <description value="Simple product type switching on editing to configurable product"/> + <testCaseId value="MAGETWO-29633"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <comment userInput="Create product" stepKey="commentCreateProduct"/> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + <!--Create attribute with options--> + <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Add configurations to product--> + <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveConfigProductForm"/> + <!--Assert configurable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <!--Assert configurable product on storefront--> + <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> + <see userInput="option1" stepKey="verifyOption1Exists"/> + <see userInput="option2" stepKey="verifyOption2Exists"/> + </test> + <test name="AdminConfigurableProductTypeSwitchingToVirtualProductTest" extends="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Configurable product type switching on editing to virtual product"/> + <description value="Configurable product type switching on editing to virtual product"/> + <testCaseId value="MC-17952"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <!--Delete product configurations--> + <comment userInput="Delete product configuration" stepKey="commentDeleteConfigs"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToConfigProductPage"/> + <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> + <conditionalClick selector="{{ AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> + <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption1Actions"/> + <click selector="{{AdminProductFormConfigurationsSection.removeProductBtn}}" stepKey="clickRemoveOption1"/> + <click selector="{{AdminProductFormConfigurationsSection.actionsBtn('1')}}" stepKey="clickToExpandOption2Actions"/> + <click selector="{{AdminProductFormConfigurationsSection.removeProductBtn}}" stepKey="clickRemoveOption2"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{SimpleProduct2.price}}" stepKey="fillProductPrice"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{SimpleProduct2.quantity}}" stepKey="fillProductQty"/> + <clearField selector="{{AdminProductFormSection.productWeight}}" stepKey="clearWeightField"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has no weight" stepKey="selectNoWeight"/> + <actionGroup ref="saveProductForm" stepKey="saveVirtualProductForm"/> + <!--Assert virtual product on Admin product page grid--> + <comment userInput="Assert virtual product on Admin product page grid" stepKey="commentAssertVirtualProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForVirtual"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuForVirtual"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeVirtualProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Virtual Product" stepKey="seeVirtualProductTypeInGrid"/> + <!--Assert virtual product on storefront--> + <comment userInput="Assert virtual product on storefront" stepKey="commentAssertVirtualProductOnStorefront"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openVirtualProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontVirtualProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertVirtualProductInStock"/> + </test> + <test name="AdminVirtualProductTypeSwitchingToConfigurableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Virtual product type switching on editing to configurable product"/> + <description value="Virtual product type switching on editing to configurable product"/> + <testCaseId value="MC-17953"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <comment userInput="Create product" stepKey="commentCreateProduct"/> + <createData entity="VirtualProduct" stepKey="createProduct"/> + <!--Create attribute with options--> + <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Add configurations to product--> + <comment userInput="Add configurations to product" stepKey="commentAddConfigurations"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToConfigProductPage"/> + <waitForPageLoad stepKey="waitForConfigurableProductPageLoad"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForConfigurableProduct"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurationsForProduct"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveNewConfigurableProductForm"/> + <!--Assert configurable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigurableProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfigurable"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuForConfigurable"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeConfigurableProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeConfigurableProductTypeInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfigurableProductSkuInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfigurableProductSkuInGrid2"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearConfigurableProductFilters"/> + <!--Assert configurable product on storefront--> + <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigurableProductOnStorefront"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openConfigurableProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontConfigurableProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertConfigurableProductInStock"/> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickConfigurableAttributeDropDown"/> + <see userInput="option1" stepKey="verifyConfigurableProductOption1Exists"/> + <see userInput="option2" stepKey="verifyConfigurableProductOption2Exists"/> + </test> + <test name="AdminDownloadableProductTypeSwitchingToConfigurableProductTest" extends="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> + <annotations> + <features value="Catalog"/> + <title value="Downloadable product type switching on editing to configurable product"/> + <description value="Downloadable product type switching on editing to configurable product"/> + <testCaseId value="MC-17957"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <!--Create attribute with options--> + <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + </before> + <after> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> + <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Add configurations to product--> + <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> + <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection"/> + <uncheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForProduct"/> + <actionGroup ref="saveProductForm" stepKey="saveDownloadssdableProductForm"/> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> + <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> + </actionGroup> + <actionGroup ref="saveConfiguredProduct" stepKey="saveConfigProductForm"/> + <!--Assert configurable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfig"/> + <click selector="{{AdminProductGridSection.columnHeader('ID')}}" stepKey="clickIdHeaderToSortAsc"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuFroConfig"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <!--Assert configurable product on storefront--> + <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> + <see userInput="option1" stepKey="verifyOption1Exists"/> + <see userInput="option2" stepKey="verifyOption2Exists"/> + </test> +</tests> From 170425c4ddf139ceda21288452ef06e0e464afa1 Mon Sep 17 00:00:00 2001 From: natalia <natalia_marozava@epam.com> Date: Thu, 15 Aug 2019 10:58:17 +0300 Subject: [PATCH 289/841] MC-17251: Creating a preference for category product indexer breaks setup:di:compile - Changed \Magento\Framework\Async\Code\Generator\ProxyDeferredGenerator (CR comment) --- .../Category/Product/Action/FullTest.php | 31 ++++++++++++++----- .../Helper/File/Storage/DatabaseTest.php | 5 +++ .../Code/Generator/ProxyDeferredGenerator.php | 13 +++++--- .../Code/Generator/Interceptor.php | 5 ++- .../ObjectManager/Code/Generator/Proxy.php | 1 - 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php index 59f312daca4d6..54d717747d046 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Category/Product/Action/FullTest.php @@ -3,15 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Model\Indexer\Category\Product\Action; use Magento\Catalog\Model\Indexer\Category\Product\Action\Full as OriginObject; use Magento\TestFramework\Catalog\Model\Indexer\Category\Product\Action\Full as PreferenceObject; use Magento\Framework\Interception\PluginListInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; /** - * Class FullTest - * @package Magento\Catalog\Model\Indexer\Category\Product\Action + * Test for Magento\Catalog\Model\Indexer\Category\Product\Action\Full * */ class FullTest extends \PHPUnit\Framework\TestCase { @@ -28,14 +31,28 @@ class FullTest extends \PHPUnit\Framework\TestCase private $pluginList; /** - * Prepare data for test + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritDoc */ protected function setUp() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $objectManager->configure(['preferences' => [OriginObject::class => PreferenceObject::class]]); - $this->interceptor = $objectManager->get(OriginObject::class); - $this->pluginList = $objectManager->get(PluginListInterface::class); + $this->objectManager = Bootstrap::getObjectManager(); + $preferenceObject = $this->objectManager->get(PreferenceObject::class); + $this->objectManager->addSharedInstance($preferenceObject, OriginObject::class); + $this->interceptor = $this->objectManager->get(OriginObject::class); + $this->pluginList = $this->objectManager->get(PluginListInterface::class); + } + + /** + * @inheritDoc + */ + protected function tearDown() + { + $this->objectManager->removeSharedInstance(OriginObject::class); } /** diff --git a/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php b/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php index 92a3ee4181122..a132f97b07e6e 100644 --- a/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php +++ b/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\MediaStorage\Helper\File\Storage; use Magento\Framework\ObjectManagerInterface; @@ -50,7 +52,10 @@ protected function setUp() /** * test for \Magento\MediaStorage\Model\File\Storage\Database::deleteFolder() * + * @magentoDbIsolation enabled * @magentoDataFixture Magento/MediaStorage/_files/database_mode.php + * @magentoConfigFixture current_store system/media_storage_configuration/media_storage 1 + * @magentoConfigFixture current_store system/media_storage_configuration/media_database default_setup */ public function testDeleteFolder() { diff --git a/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php b/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php index 7d64a92ab7f9d..c536c55fdfccd 100644 --- a/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php +++ b/lib/internal/Magento/Framework/Async/Code/Generator/ProxyDeferredGenerator.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Framework\Async\Code\Generator; @@ -151,7 +150,7 @@ protected function _getMethodInfo(\ReflectionMethod $method) $parameters[] = $this->_getMethodParameterInfo($parameter); } - $returnTypeValue = $this->getReturnTypeValue($method->getReturnType()); + $returnTypeValue = $this->getReturnTypeValue($method); $methodInfo = [ 'name' => $method->getName(), 'parameters' => $parameters, @@ -208,6 +207,7 @@ protected function _getMethodBody( } else { $methodCall = sprintf('%s(%s)', $name, implode(', ', $parameters)); } + //Waiting for deferred result and using it's methods. return "\$this->wait();\n" .($withoutReturn ? '' : 'return ')."\$this->instance->$methodCall;"; @@ -231,24 +231,27 @@ protected function _validateData() $result = false; } } + return $result; } /** * Returns return type * - * @param mixed $returnType + * @param \ReflectionMethod $method * @return null|string */ - private function getReturnTypeValue($returnType): ?string + private function getReturnTypeValue(\ReflectionMethod $method): ?string { $returnTypeValue = null; + $returnType = $method->getReturnType(); if ($returnType) { $returnTypeValue = ($returnType->allowsNull() ? '?' : ''); $returnTypeValue .= ($returnType->getName() === 'self') - ? $this->getSourceClassName() + ? $this->_getFullyQualifiedClassName($method->getDeclaringClass()->getName()) : $returnType->getName(); } + return $returnTypeValue; } } diff --git a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php index 624dc21759640..69a0d3029e18d 100644 --- a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Framework\Interception\Code\Generator; @@ -137,8 +136,8 @@ protected function _getMethodInfo(\ReflectionMethod $method) } METHOD_BODY ), - 'returnType' => $returnTypeValue, - 'docblock' => ['shortDescription' => '{@inheritdoc}'], + 'returnType' => $returnTypeValue, + 'docblock' => ['shortDescription' => '{@inheritdoc}'], ]; return $methodInfo; diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php index 8d69cce209c00..a45795e5df16b 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php @@ -5,7 +5,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Framework\ObjectManager\Code\Generator; From db851848fc088e512f22c6347d77c0f0e4f98852 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Tue, 20 Aug 2019 16:08:16 +0300 Subject: [PATCH 290/841] MC-19260: Coupon code removed during tax/shipping calculation on checkout --- app/code/Magento/SalesRule/Model/Validator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/SalesRule/Model/Validator.php b/app/code/Magento/SalesRule/Model/Validator.php index ea0221d8f072d..b272aa73af42a 100644 --- a/app/code/Magento/SalesRule/Model/Validator.php +++ b/app/code/Magento/SalesRule/Model/Validator.php @@ -244,6 +244,8 @@ public function reset(Address $address) { $this->validatorUtility->resetRoundingDeltas(); if ($this->_isFirstTimeResetRun) { + $address->setBaseSubtotalWithDiscount($address->getBaseSubtotal()); + $address->setSubtotalWithDiscount($address->getSubtotal()); $address->setAppliedRuleIds(''); $address->getQuote()->setAppliedRuleIds(''); $this->_isFirstTimeResetRun = false; From 8746079fafa075b9223658d68d0b8f69f651cba3 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Tue, 20 Aug 2019 16:45:39 +0300 Subject: [PATCH 291/841] magento/magento2#20765: Static test fix. --- .../Adminhtml/Widget/Instance/MassDelete.php | 18 ++++++++++++------ .../Magento/Widget/Model/DeleteWidgetById.php | 2 ++ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php index af9ceeb725c0a..1134dd219059b 100644 --- a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php +++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php @@ -71,7 +71,7 @@ public function execute(): Redirect foreach ($instanceIds as $instanceId) { try { - $this->deleteWidgetById->execute((int) $instanceId); + $this->deleteWidgetById->execute((int)$instanceId); $deletedInstances++; } catch (NoSuchEntityException $e) { $notDeletedInstances[] = $instanceId; @@ -83,10 +83,12 @@ public function execute(): Redirect } if (count($notDeletedInstances)) { - $this->messageManager->addErrorMessage(__( - 'Widget(s) with ID(s) %1 were not found', - trim(implode(', ', $notDeletedInstances)) - )); + $this->messageManager->addErrorMessage( + __( + 'Widget(s) with ID(s) %1 were not found', + trim(implode(', ', $notDeletedInstances)) + ) + ); } /** @var Redirect $resultRedirect */ @@ -96,6 +98,8 @@ public function execute(): Redirect } /** + * Get instance IDs. + * * @return array */ private function getInstanceIds(): array @@ -110,7 +114,9 @@ private function getInstanceIds(): array } /** - * @return ResultInterface|RedirectInterface + * Get result page. + * + * @return ResultInterface|null */ private function getResultPage(): ?ResultInterface { diff --git a/app/code/Magento/Widget/Model/DeleteWidgetById.php b/app/code/Magento/Widget/Model/DeleteWidgetById.php index 1c27e6e1e9ed3..dc48f6896293a 100644 --- a/app/code/Magento/Widget/Model/DeleteWidgetById.php +++ b/app/code/Magento/Widget/Model/DeleteWidgetById.php @@ -54,6 +54,8 @@ public function execute(int $instanceId) : void } /** + * Get widget instance by given instance ID + * * @param int $instanceId * @return WidgetInstance * @throws NoSuchEntityException From 91f30aa65658b0776269c8fcdd92569c824c328e Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Tue, 20 Aug 2019 17:23:13 +0300 Subject: [PATCH 292/841] MC-19105: One item purchased turns into two items on reorder (Multi-shipping) --- .../quote_with_configurable_product.php | 145 ++++++++++++++++++ ...ote_with_configurable_product_rollback.php | 17 ++ .../Model/Checkout/Type/MultishippingTest.php | 36 +++++ 3 files changed, 198 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php create mode 100644 dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php b/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php new file mode 100644 index 0000000000000..df6854aed1971 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php @@ -0,0 +1,145 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Checkout\Model\Session; +use Magento\Eav\Model\Config; +use Magento\Framework\DataObject; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address\Rate; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +require __DIR__ . '/../../ConfigurableProduct/_files/configurable_products.php'; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +$product = $productRepository->getById(10); +$product->setStockData(['use_config_manage_stock' => 1, 'qty' => 2, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); +$productRepository->save($product); + +/** @var Quote $quote */ +$quote = $objectManager->create(Quote::class); +$request = $objectManager->create(DataObject::class); + +/** @var Config $eavConfig */ +$eavConfig = $objectManager->get(Config::class); +/** @var $attribute */ +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + +$request->setData( + [ + 'product_id' => $productRepository->get('configurable')->getId(), + 'selected_configurable_option' => '1', + 'super_attribute' => [ + $attribute->getAttributeId() => $attribute->getOptions()[1]->getValue() + ], + 'qty' => '2' + ] +); + +$quote->setStoreId(1) + ->setIsActive( + true + )->setIsMultiShipping( + 1 + )->setReservedOrderId( + 'test_order_with_configurable_product' + )->setCustomerEmail( + 'store@example.com' + )->addProduct( + $productRepository->get('configurable'), + $request + ); + +/** @var PaymentInterface $payment */ +$payment = $objectManager->create(PaymentInterface::class); +$payment->setMethod('checkmo'); +$quote->setPayment($payment); + +$addressList = [ + [ + 'firstname' => 'Jonh', + 'lastname' => 'Doe', + 'telephone' => '0333-233-221', + 'street' => ['Main Division 1'], + 'city' => 'Culver City', + 'region' => 'CA', + 'postcode' => 90800, + 'country_id' => 'US', + 'email' => 'store@example.com', + 'address_type' => 'shipping', + ], + [ + 'firstname' => 'Antoni', + 'lastname' => 'Holmes', + 'telephone' => '0333-233-221', + 'street' => ['Second Division 2'], + 'city' => 'Denver', + 'region' => 'CO', + 'postcode' => 80203, + 'country_id' => 'US', + 'email' => 'customer002@shipping.test', + 'address_type' => 'shipping' + ], +]; + +$methodCode = 'flatrate_flatrate'; +foreach ($addressList as $data) { + /** @var Rate $rate */ + $rate = $objectManager->create(Rate::class); + $rate->setCode($methodCode) + ->setPrice(5.00); + + $address = $objectManager->create(AddressInterface::class, ['data' => $data]); + $address->setShippingMethod($methodCode) + ->addShippingRate($rate) + ->setShippingAmount(5.00) + ->setBaseShippingAmount(5.00); + + $quote->addAddress($address); +} + +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->get(CartRepositoryInterface::class); +$quoteRepository->save($quote); + +$items = $quote->getAllItems(); + +foreach ($quote->getAllShippingAddresses() as $address) { + foreach ($items as $item) { + $item->setQty(1); + $address->setTotalQty(1); + $address->addItem($item); + }; +} + +$billingAddressData = [ + 'firstname' => 'Jonh', + 'lastname' => 'Doe', + 'telephone' => '0333-233-221', + 'street' => ['Third Division 1'], + 'city' => 'New York', + 'region' => 'NY', + 'postcode' => 10029, + 'country_id' => 'US', + 'email' => 'store@example.com', + 'address_type' => 'billing', +]; + +/** @var AddressInterface $address */ +$billingAddress = $objectManager->create(AddressInterface::class, ['data' => $billingAddressData]); +$quote->setBillingAddress($billingAddress); +$quote->collectTotals(); +$quoteRepository->save($quote); + +/** @var Session $session */ +$session = $objectManager->get(Session::class); +$session->setQuoteId($quote->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product_rollback.php b/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product_rollback.php new file mode 100644 index 0000000000000..578fcd9855de3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product_rollback.php @@ -0,0 +1,17 @@ +<?php +/** + * Rollback for quote_with_configurable_product_last_variation.php fixture. + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Quote\Model\Quote; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var $objectManager ObjectManager */ +$objectManager = Bootstrap::getObjectManager(); +$quote = $objectManager->create(Quote::class); +$quote->load('test_order_with_configurable_product', 'reserved_order_id')->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Multishipping/Model/Checkout/Type/MultishippingTest.php b/dev/tests/integration/testsuite/Magento/Multishipping/Model/Checkout/Type/MultishippingTest.php index b4222c0d0189d..ef9e65156c0b5 100644 --- a/dev/tests/integration/testsuite/Magento/Multishipping/Model/Checkout/Type/MultishippingTest.php +++ b/dev/tests/integration/testsuite/Magento/Multishipping/Model/Checkout/Type/MultishippingTest.php @@ -361,6 +361,42 @@ public function testCreateOrdersWithSomeFailedOrders() ); } + /** + * Check product parent item id in order item + * + * @magentoDataFixture Magento/Multishipping/Fixtures/quote_with_configurable_product.php + */ + public function testCreateOrdersWithConfigurableProduct() + { + $quote = $this->getQuote('test_order_with_configurable_product'); + /** @var CheckoutSession $session */ + $session = $this->objectManager->get(CheckoutSession::class); + $session->replaceQuote($quote); + + $this->model->createOrders(); + + $quoteItemParentIds = []; + foreach ($quote->getAllItems() as $quoteItem) { + $quoteItemParentIds[] = $quoteItem->getParentItemId(); + } + + $orderList = $this->getOrderList((int)$quote->getId()); + $firstOrder = array_shift($orderList); + $secondOrder = array_shift($orderList); + + $firstOrderItemParentsIds = []; + foreach ($firstOrder->getItems() as $orderItem) { + $firstOrderItemParentsIds[] = $orderItem->getParentItemId(); + } + $secondOrderItemParentsIds = []; + foreach ($secondOrder->getItems() as $orderItem) { + $secondOrderItemParentsIds[] = $orderItem->getParentItemId(); + } + + $this->assertNotNull($firstOrderItemParentsIds[1]); + $this->assertNotNull($secondOrderItemParentsIds[1]); + } + /** * Returns order service mock with successful place on first call and exceptions on other calls. * From 2738146f7be71d1b8603da455ad2d9990252cc09 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Tue, 20 Aug 2019 09:42:43 -0500 Subject: [PATCH 293/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../UseCase/QueueTestCaseAbstract.php | 37 ++++++++++++++----- .../UseCase/WaitAndNotWaitMessagesTest.php | 7 +--- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php index ce1177fd20175..e8f07d3135f49 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php @@ -47,16 +47,22 @@ class QueueTestCaseAbstract extends \PHPUnit\Framework\TestCase */ protected $publisherConsumerController; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); $this->logFilePath = TESTS_TEMP_DIR . "/MessageQueueTestLog.txt"; - $this->publisherConsumerController = $this->objectManager->create(PublisherConsumerController::class, [ - 'consumers' => $this->consumers, - 'logFilePath' => $this->logFilePath, - 'maxMessages' => $this->maxMessages, - 'appInitParams' => \Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppInitParams() - ]); + $this->publisherConsumerController = $this->objectManager->create( + PublisherConsumerController::class, + [ + 'consumers' => $this->consumers, + 'logFilePath' => $this->logFilePath, + 'maxMessages' => $this->maxMessages, + 'appInitParams' => \Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppInitParams() + ] + ); try { $this->publisherConsumerController->initialize(); @@ -70,6 +76,9 @@ protected function setUp() $this->publisher = $this->publisherConsumerController->getPublisher(); } + /** + * @inheritdoc + */ protected function tearDown() { $this->publisherConsumerController->stopConsumers(); @@ -85,16 +94,24 @@ protected function waitForAsynchronousResult($expectedLinesCount, $logFilePath) { try { //$expectedLinesCount, $logFilePath - $this->publisherConsumerController->waitForAsynchronousResult([$this, 'checkLogsExists'], [ - $expectedLinesCount, $logFilePath - ]); + $this->publisherConsumerController->waitForAsynchronousResult( + [$this, 'checkLogsExists'], + [$expectedLinesCount, $logFilePath] + ); } catch (PreconditionFailedException $e) { $this->fail($e->getMessage()); } } + /** + * Checks that logs exist + * + * @param int $expectedLinesCount + * @return bool + */ public function checkLogsExists($expectedLinesCount) { + //phpcs:ignore Magento2.Functions.DiscouragedFunction $actualCount = file_exists($this->logFilePath) ? count(file($this->logFilePath)) : 0; return $expectedLinesCount === $actualCount; } @@ -102,7 +119,7 @@ public function checkLogsExists($expectedLinesCount) /** * Workaround for https://bugs.php.net/bug.php?id=72286 */ - public static function tearDownAfterClass() + public function tearDownAfterClass() { if (version_compare(phpversion(), '7') == -1) { $closeConnection = new \ReflectionMethod(\Magento\Amqp\Model\Config::class, 'closeConnection'); diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php index 331ebaed3b89e..62040018ffd3e 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php @@ -23,18 +23,13 @@ class WaitAndNotWaitMessagesTest extends QueueTestCaseAbstract */ private $filesystem; - /** - * @var ConfigFilePool - */ - private $configFilePool; - /** * @var array */ private $config; /** - * @var \Magento\TestModuleAsyncAmqp\Model\AsyncTestData + * @var AsyncTestData */ protected $msgObject; From a5d430030c198e6627a8774efe237077b0df95f6 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Tue, 20 Aug 2019 09:55:23 -0500 Subject: [PATCH 294/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../Magento/MessageQueue/Setup/ConfigOptionsList.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php index d3e559e14e152..629d36a9f9f55 100644 --- a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php +++ b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php @@ -31,8 +31,13 @@ class ConfigOptionsList implements ConfigOptionsListInterface /** * Default value */ - const DEFULT_CONSUMERS_WAIT_FOR_MESSAGES = 1; + const DEFAULT_CONSUMERS_WAIT_FOR_MESSAGES = 1; + /** + * The available configuration values + * + * @var array + */ private $selectOptions = [0, 1]; /** @@ -47,7 +52,7 @@ public function getOptions() $this->selectOptions, self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES, 'Should consumers wait for a message from the queue? 1 - Yes, 0 - No', - self::DEFULT_CONSUMERS_WAIT_FOR_MESSAGES + self::DEFAULT_CONSUMERS_WAIT_FOR_MESSAGES ), ]; } From e20242cd4b3986036feb39df948f16caf678abe2 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Tue, 20 Aug 2019 10:02:17 -0500 Subject: [PATCH 295/841] MC-19250: The stuck deployment on the Cloud because of consumers --- app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php index 629d36a9f9f55..b23dc7fbbf532 100644 --- a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php +++ b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php @@ -72,7 +72,7 @@ public function createConfig(array $data, DeploymentConfig $deploymentConfig) ); } - return[$configData]; + return [$configData]; } /** From b9646cea8324f2ca01da81a196d0e44fabf23644 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 20 Aug 2019 22:04:51 +0700 Subject: [PATCH 296/841] Resolve Some Fields in "Currency Setup" must have validate number issue24204 --- app/code/Magento/Directory/etc/adminhtml/system.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Directory/etc/adminhtml/system.xml b/app/code/Magento/Directory/etc/adminhtml/system.xml index 899453c771e4b..7d650b14b3d97 100644 --- a/app/code/Magento/Directory/etc/adminhtml/system.xml +++ b/app/code/Magento/Directory/etc/adminhtml/system.xml @@ -43,6 +43,7 @@ </field> <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Connection Timeout in Seconds</label> + <validate>validate-zero-or-greater validate-number</validate> </field> </group> <group id="currencyconverterapi" translate="label" sortOrder="45" showInDefault="1" showInWebsite="0" showInStore="0"> @@ -54,6 +55,7 @@ </field> <field id="timeout" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Connection Timeout in Seconds</label> + <validate>validate-zero-or-greater validate-number</validate> </field> </group> <group id="import" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0"> From aec715786dfebef32ab25b7f9c9b582898d5827e Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Tue, 20 Aug 2019 18:19:55 +0300 Subject: [PATCH 297/841] MAGETWO-70885: The 'incrementId' property of the order with state 'complete' is increased after order status update - Add webapi tests for checking increment_id after shipment, invoice and creditmemo. --- .../Magento/Sales/Model/OrderRepository.php | 1 + .../Model/ResourceModel/EntityAbstract.php | 2 + .../Unit/Model/ResourceModel/OrderTest.php | 2 + .../Sales/Service/V1/OrderUpdateTest.php | 305 +++++++++++++++++- 4 files changed, 299 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index bb523f0f91b36..f93de4c32d888 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Sales\Model; diff --git a/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php b/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php index 812ada3decb91..72ce60d32877c 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php +++ b/app/code/Magento/Sales/Model/ResourceModel/EntityAbstract.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Model\ResourceModel; use Magento\Framework\Model\ResourceModel\Db\VersionControl\AbstractDb; diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php index 2b281b71fb0ff..067e44757a67b 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/OrderTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Test\Unit\Model\ResourceModel; use Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite; diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php index df47ae3220467..d4bfbfc177390 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderUpdateTest.php @@ -3,8 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Service\V1; +use Magento\Sales\Model\Order; use Magento\TestFramework\TestCase\WebapiAbstract; use Magento\Sales\Api\Data\OrderInterface; @@ -13,18 +16,18 @@ */ class OrderUpdateTest extends WebapiAbstract { - const RESOURCE_PATH = '/V1/orders'; + private const RESOURCE_PATH = '/V1/orders'; - const SERVICE_NAME = 'salesOrderRepositoryV1'; + private const SERVICE_NAME = 'salesOrderRepositoryV1'; - const SERVICE_VERSION = 'V1'; + private const SERVICE_VERSION = 'V1'; - const ORDER_INCREMENT_ID = '100000001'; + private const ORDER_INCREMENT_ID = '100000001'; /** * @var \Magento\Framework\ObjectManagerInterface */ - protected $objectManager; + private $objectManager; /** * @inheritDoc @@ -41,11 +44,104 @@ protected function setUp() */ public function testOrderUpdate() { - /** @var \Magento\Sales\Model\Order $order */ - $order = $this->objectManager->get(\Magento\Sales\Model\Order::class) + /** @var Order $order */ + $order = $this->objectManager->get(Order::class)->loadByIncrementId(self::ORDER_INCREMENT_ID); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'save', + ], + ]; + $result = $this->_webApiCall($serviceInfo, ['entity' => $this->getOrderData($order)]); + $this->assertGreaterThan(1, count($result)); + /** @var Order $actualOrder */ + $actualOrder = $this->objectManager->get(Order::class)->load($order->getId()); + $this->assertEquals( + $order->getData(OrderInterface::INCREMENT_ID), + $actualOrder->getData(OrderInterface::INCREMENT_ID) + ); + + //Ship the order and check increment id. + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/order/' . $order->getId() . '/ship', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => 'salesShipOrderV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'salesShipOrderV1' . 'execute', + ], + ]; + $shipmentId = $this->_webApiCall($serviceInfo, $this->getDataForShipment($order)); + $this->assertNotEmpty($shipmentId); + $actualOrder = $this->objectManager->get(Order::class)->load($order->getId()); + $this->assertEquals( + $order->getData(OrderInterface::INCREMENT_ID), + $actualOrder->getData(OrderInterface::INCREMENT_ID) + ); + + //Invoice the order and check increment id. + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/invoices', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => 'salesInvoiceRepositoryV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'salesInvoiceRepositoryV1' . 'save', + ], + ]; + + $result = $this->_webApiCall($serviceInfo, ['entity' => $this->getDataForInvoice($order)]); + $this->assertNotEmpty($result); + $actualOrder = $this->objectManager->get(Order::class)->load($order->getId()); + $this->assertEquals( + $order->getData(OrderInterface::INCREMENT_ID), + $actualOrder->getData(OrderInterface::INCREMENT_ID) + ); + + //Create creditmemo for the order and check increment id. + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/creditmemo', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => 'salesCreditmemoRepositoryV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'salesCreditmemoRepositoryV1' . 'save', + ], + ]; + $result = $this->_webApiCall($serviceInfo, ['entity' => $this->getDataForCreditmemo($order)]); + $this->assertNotEmpty($result); + $actualOrder = $this->objectManager->get(Order::class)->load($order->getId()); + $this->assertEquals( + $order->getData(OrderInterface::INCREMENT_ID), + $actualOrder->getData(OrderInterface::INCREMENT_ID) + ); + } + + /** + * Check order increment id after updating via webapi + * + * @magentoApiDataFixture Magento/Sales/_files/order.php + */ + public function testOrderStatusUpdate() + { + /** @var Order $order */ + $order = $this->objectManager->get(Order::class) ->loadByIncrementId(self::ORDER_INCREMENT_ID); $entityData = $this->getOrderData($order); + $entityData[OrderInterface::STATE] = 'complete'; + $entityData[OrderInterface::STATUS] = 'complete'; $requestData = ['entity' => $entityData]; @@ -63,8 +159,8 @@ public function testOrderUpdate() $result = $this->_webApiCall($serviceInfo, $requestData); $this->assertGreaterThan(1, count($result)); - /** @var \Magento\Sales\Model\Order $actualOrder */ - $actualOrder = $this->objectManager->get(\Magento\Sales\Model\Order::class)->load($order->getId()); + /** @var Order $actualOrder */ + $actualOrder = $this->objectManager->get(Order::class)->load($order->getId()); $this->assertEquals( $order->getData(OrderInterface::INCREMENT_ID), $actualOrder->getData(OrderInterface::INCREMENT_ID) @@ -74,10 +170,10 @@ public function testOrderUpdate() /** * Prepare order data for request * - * @param \Magento\Sales\Model\Order $order + * @param Order $order * @return array */ - private function getOrderData(\Magento\Sales\Model\Order $order) + private function getOrderData(Order $order) { if (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) { $entityData = $order->getData(); @@ -116,4 +212,191 @@ private function getOrderData(\Magento\Sales\Model\Order $order) } return $orderData; } + + /** + * Get data for invoice from order. + * + * @param Order $order + * @return array + */ + private function getDataForInvoice(Order $order): array + { + $orderItems = $order->getAllItems(); + return [ + 'order_id' => $order->getId(), + 'base_currency_code' => null, + 'base_discount_amount' => null, + 'base_grand_total' => null, + 'base_discount_tax_compensation_amount' => null, + 'base_shipping_amount' => null, + 'base_shipping_discount_tax_compensation_amnt' => null, + 'base_shipping_incl_tax' => null, + 'base_shipping_tax_amount' => null, + 'base_subtotal' => null, + 'base_subtotal_incl_tax' => null, + 'base_tax_amount' => null, + 'base_total_refunded' => null, + 'base_to_global_rate' => null, + 'base_to_order_rate' => null, + 'billing_address_id' => null, + 'can_void_flag' => null, + 'created_at' => null, + 'discount_amount' => null, + 'discount_description' => null, + 'email_sent' => null, + 'entity_id' => null, + 'global_currency_code' => null, + 'grand_total' => null, + 'discount_tax_compensation_amount' => null, + 'increment_id' => null, + 'is_used_for_refund' => null, + 'order_currency_code' => null, + 'shipping_address_id' => null, + 'shipping_amount' => null, + 'shipping_discount_tax_compensation_amount' => null, + 'shipping_incl_tax' => null, + 'shipping_tax_amount' => null, + 'state' => null, + 'store_currency_code' => null, + 'store_id' => null, + 'store_to_base_rate' => null, + 'store_to_order_rate' => null, + 'subtotal' => null, + 'subtotal_incl_tax' => null, + 'tax_amount' => null, + 'total_qty' => '1', + 'transaction_id' => null, + 'updated_at' => null, + 'items' => [ + [ + 'orderItemId' => $orderItems[0]->getId(), + 'qty' => 2, + 'additionalData' => null, + 'baseCost' => null, + 'baseDiscountAmount' => null, + 'baseDiscountTaxCompensationAmount' => null, + 'basePrice' => null, + 'basePriceInclTax' => null, + 'baseRowTotal' => null, + 'baseRowTotalInclTax' => null, + 'baseTaxAmount' => null, + 'description' => null, + 'discountAmount' => null, + 'discountTaxCompensationAmount' => null, + 'name' => null, + 'entity_id' => null, + 'parentId' => null, + 'price' => null, + 'priceInclTax' => null, + 'productId' => null, + 'rowTotal' => null, + 'rowTotalInclTax' => null, + 'sku' => 'sku' . uniqid(), + 'taxAmount' => null, + ], + ], + ]; + } + + /** + * Get data for creditmemo. + * + * @param Order $order + * @return array + */ + private function getDataForCreditmemo(Order $order): array + { + $orderItem = current($order->getAllItems()); + $items = [ + $orderItem->getId() => ['order_item_id' => $orderItem->getId(), 'qty' => $orderItem->getQtyInvoiced()], + ]; + return [ + 'adjustment' => null, + 'adjustment_negative' => null, + 'adjustment_positive' => null, + 'base_adjustment' => null, + 'base_adjustment_negative' => null, + 'base_adjustment_positive' => null, + 'base_currency_code' => null, + 'base_discount_amount' => null, + 'base_grand_total' => null, + 'base_discount_tax_compensation_amount' => null, + 'base_shipping_amount' => null, + 'base_shipping_discount_tax_compensation_amnt' => null, + 'base_shipping_incl_tax' => null, + 'base_shipping_tax_amount' => null, + 'base_subtotal' => null, + 'base_subtotal_incl_tax' => null, + 'base_tax_amount' => null, + 'base_to_global_rate' => null, + 'base_to_order_rate' => null, + 'billing_address_id' => null, + 'created_at' => null, + 'creditmemo_status' => null, + 'discount_amount' => null, + 'discount_description' => null, + 'email_sent' => null, + 'entity_id' => null, + 'global_currency_code' => null, + 'grand_total' => null, + 'discount_tax_compensation_amount' => null, + 'increment_id' => null, + 'invoice_id' => null, + 'order_currency_code' => null, + 'order_id' => $order->getId(), + 'shipping_address_id' => null, + 'shipping_amount' => null, + 'shipping_discount_tax_compensation_amount' => null, + 'shipping_incl_tax' => null, + 'shipping_tax_amount' => null, + 'state' => null, + 'store_currency_code' => null, + 'store_id' => null, + 'store_to_base_rate' => null, + 'store_to_order_rate' => null, + 'subtotal' => null, + 'subtotal_incl_tax' => null, + 'tax_amount' => null, + 'transaction_id' => null, + 'updated_at' => null, + 'items' => $items, + ]; + } + + /** + * Get data for shipment. + * + * @param Order $order + * @return array + */ + private function getDataForShipment(Order $order): array + { + $requestShipData = [ + 'orderId' => $order->getId(), + 'items' => [], + 'comment' => [ + 'comment' => 'Test Comment', + 'is_visible_on_front' => 1, + ], + 'tracks' => [ + [ + 'track_number' => 'TEST_TRACK_0001', + 'title' => 'Simple shipment track', + 'carrier_code' => 'UPS' + ] + ] + ]; + + foreach ($order->getAllItems() as $item) { + if ($item->getProductType() == 'simple') { + $requestShipData['items'][] = [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ]; + break; + } + } + + return $requestShipData; + } } From 3b110166c45c30265124d1ae491447337c22f5b6 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Tue, 20 Aug 2019 13:57:27 -0500 Subject: [PATCH 298/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - clean cache to regenerate schema --- .../GraphQl/Catalog/ProductSearchTest.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 709779e88da54..3284a4a7172fe 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -13,6 +13,7 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\CategoryLinkManagement; +use Magento\Framework\Config\Data; use Magento\Framework\EntityManager\MetadataPool; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -21,6 +22,7 @@ use Magento\Eav\Api\Data\AttributeOptionInterface; use Magento\Framework\DataObject; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CacheCleaner; /** * @SuppressWarnings(PHPMD.TooManyPublicMethods) @@ -37,6 +39,7 @@ class ProductSearchTest extends GraphQlAbstract */ public function testFilterLn() { + CacheCleaner::cleanAll(); $query = <<<QUERY { products ( @@ -99,6 +102,7 @@ public function testFilterLn() */ public function testAdvancedSearchByOneCustomAttribute() { + CacheCleaner::cleanAll(); $attributeCode = 'second_test_configurable'; $optionValue = $this->getDefaultAttributeOptionValue($attributeCode); $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $optionValue); @@ -174,6 +178,7 @@ private function getDefaultAttributeOptionValue(string $attributeCode) : string */ public function testFullTextSearchForProductAndFilterByCustomAttribute() { + CacheCleaner::cleanAll(); $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); $query = <<<QUERY @@ -272,7 +277,7 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() } /** - * Filter by mu;ltiple attributes like category_id and custom attribute + * Filter by category_id and custom attribute * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -362,8 +367,11 @@ public function testFilterByCategoryIdAndCustomAttribute() ) ; } } - - private function getQueryProductsWithCustomAttribute($attributeCode, $optionValue) + /** + * + * @return string + */ + private function getQueryProductsWithCustomAttribute($attributeCode, $optionValue) : string { return <<<QUERY { @@ -401,7 +409,6 @@ private function getQueryProductsWithCustomAttribute($attributeCode, $optionValu } } QUERY; - } /** @@ -411,7 +418,7 @@ private function getQueryProductsWithCustomAttribute($attributeCode, $optionValu * @magentoApiDataFixture Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testLayeredNavigationForConfigurableProductWithOutOfStockOption() + public function testLayeredNavigationWithConfigurableChildrenOutOfStock() { $attributeCode = 'test_configurable'; /** @var \Magento\Eav\Model\Config $eavConfig */ @@ -964,7 +971,7 @@ public function testFilteringForProductInMultipleCategories() * @magentoApiDataFixture Magento/Catalog/_files/product_in_multiple_categories.php * @return void */ - public function testFilterProductsByCategoryIds() + public function testFilterProductsBySingleCategoryId() { $queryCategoryId = 333; $query From 8b1e739cbbbf5afa4b3d122a44d9a0c61c98f214 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Tue, 20 Aug 2019 19:44:04 +0000 Subject: [PATCH 299/841] Corrected Code Style --- .../testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php | 2 +- .../testsuite/Magento/Customer/_files/customer_subscribe.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index 2ff9026cb6a89..d6c7e590639f0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -241,7 +241,7 @@ public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput() $this->graphQlMutation($query); } - /** + /** * @expectedException \Exception * @expectedExceptionMessage Required parameters are missing: First Name */ diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php index 9585ad8b02740..66ec54641d74e 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php @@ -13,4 +13,4 @@ false, 'default', 0 -); \ No newline at end of file +); From 650bff0ae7a29c32b0576e4914d31860b083e0a6 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Tue, 20 Aug 2019 15:35:55 -0500 Subject: [PATCH 300/841] MC:18945: Reading deprecated annotation in schema - Added code for readibg deprecated annotation on schema --- .../GraphQl/Config/Element/Argument.php | 21 ++++++++++- .../Config/Element/ArgumentFactory.php | 5 +-- .../GraphQl/Config/Element/EnumFactory.php | 5 +-- .../GraphQl/Config/Element/EnumValue.php | 19 +++++++++- .../Config/Element/EnumValueFactory.php | 11 ++++-- .../GraphQl/Config/Element/Field.php | 24 +++++++++++-- .../GraphQl/Config/Element/FieldFactory.php | 5 +-- .../GraphQl/Config/Element/Input.php | 20 ++++++++++- .../GraphQl/Config/Element/InputFactory.php | 3 +- .../GraphQl/Schema/Type/Enum/Enum.php | 5 +-- .../Output/ElementMapper/Formatter/Fields.php | 6 ++++ .../MetaReader/DeprecatedAnnotationReader.php | 35 +++++++++++++++++++ .../DeprecatedEnumAnnotationReader.php | 34 ++++++++++++++++++ .../MetaReader/FieldMetaReader.php | 26 +++++++++++--- .../GraphQlReader/Reader/EnumType.php | 28 ++++++++++++--- .../GraphQlReader/Reader/ObjectType.php | 20 +++++++++-- 16 files changed, 238 insertions(+), 29 deletions(-) create mode 100644 lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php create mode 100644 lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php index 39d767fa74ad8..6a8ab2e61ecba 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php @@ -54,6 +54,11 @@ class Argument implements FieldInterface */ private $defaultValue; + /** + * @var array + */ + private $deprecated; + /** * @param string $name * @param string $type @@ -64,6 +69,8 @@ class Argument implements FieldInterface * @param string $itemType * @param bool $itemsRequired * @param string $defaultValue + * @param array $deprecated + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( string $name, @@ -74,7 +81,8 @@ public function __construct( bool $isList, string $itemType = '', bool $itemsRequired = false, - string $defaultValue = null + string $defaultValue = null, + array $deprecated = [] ) { $this->name = $name; $this->type = $isList ? $itemType : $type; @@ -84,6 +92,7 @@ public function __construct( $this->isList = $isList; $this->itemsRequired = $itemsRequired; $this->defaultValue = $defaultValue; + $this->deprecated = $deprecated; } /** @@ -175,4 +184,14 @@ public function hasDefaultValue() : bool { return $this->defaultValue ? true: false; } + + /** + * Return the deprecated + * + * @return array + */ + public function getDeprecated() : array + { + return $this->deprecated; + } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php index 86eee7afd13bd..79114ae1d0e45 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php @@ -10,7 +10,7 @@ use Magento\Framework\ObjectManagerInterface; /** - * {@inheritdoc} + * @inheritdoc */ class ArgumentFactory { @@ -51,7 +51,8 @@ public function createFromConfigData( 'isList' => isset($argumentData['itemType']), 'itemType' => isset($argumentData['itemType']) ? $argumentData['itemType'] : '', 'itemsRequired' => isset($argumentData['itemsRequired']) ? $argumentData['itemsRequired'] : false, - 'defaultValue' => isset($argumentData['defaultValue']) ? $argumentData['defaultValue'] : null + 'defaultValue' => isset($argumentData['defaultValue']) ? $argumentData['defaultValue'] : null, + 'deprecated' => isset($argumentData['deprecated']) ? $argumentData['deprecated'] : [], ] ); } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php index 3e0974aedd473..f8b42f5611f30 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php @@ -12,7 +12,7 @@ use Magento\Framework\ObjectManagerInterface; /** - * {@inheritdoc} + * @inheritdoc */ class EnumFactory implements ConfigElementFactoryInterface { @@ -71,7 +71,8 @@ public function createFromConfigData(array $data): ConfigElementInterface $values[$item['_value']] = $this->enumValueFactory->create( $item['name'], $item['_value'], - isset($item['description']) ? $item['description'] : '' + isset($item['description']) ? $item['description'] : '', + isset($item['deprecationReason']) ? $item['deprecationReason'] : '' ); } return $this->create( diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php index 151c07caf16a2..7cb48b40a64d5 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php @@ -29,16 +29,23 @@ class EnumValue implements ConfigElementInterface */ private $description; + /** + * @var string + */ + private $deprecationReason; + /** * @param string $name * @param string $value * @param string $description + * @param string deprecationReason */ - public function __construct(string $name, string $value, string $description = '') + public function __construct(string $name, string $value, string $description = '', string $deprecationReason = '') { $this->name = $name; $this->value = $value; $this->description = $description; + $this->deprecationReason = $deprecationReason; } /** @@ -70,4 +77,14 @@ public function getDescription() : string { return $this->description; } + + /** + * Get the enum value's description. + * + * @return string + */ + public function getDeprecatedReason() : string + { + return $this->deprecationReason; + } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php index c5fb75fb3566c..804e4334f0fb2 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php @@ -34,16 +34,21 @@ public function __construct( * @param string $name * @param string $value * @param string $description + * @param string $deprecationReason * @return EnumValue */ - public function create(string $name, string $value, string $description = ''): EnumValue - { + public function create( + string $name, string $value, + string $description = '', + string $deprecationReason = '' + ): EnumValue { return $this->objectManager->create( EnumValue::class, [ 'name' => $name, 'value' => $value, - 'description' => $description + 'description' => $description, + 'deprecationReason' => $deprecationReason ] ); } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php index 0fc51e4ecd069..a01749bebe694 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php @@ -53,6 +53,11 @@ class Field implements OutputFieldInterface */ private $cache; + /** + * @var array + */ + private $deprecated; + /** * @param string $name * @param string $type @@ -63,6 +68,8 @@ class Field implements OutputFieldInterface * @param string $description * @param array $arguments * @param array $cache + * @param array $deprecated + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( string $name, @@ -73,7 +80,8 @@ public function __construct( string $resolver = '', string $description = '', array $arguments = [], - array $cache = [] + array $cache = [], + array $deprecated = [] ) { $this->name = $name; $this->type = $isList ? $itemType : $type; @@ -83,6 +91,7 @@ public function __construct( $this->description = $description; $this->arguments = $arguments; $this->cache = $cache; + $this->deprecated = $deprecated; } /** @@ -156,12 +165,21 @@ public function getDescription() : string } /** - * Return the cache tag for the field. + * Return the cache * - * @return array|null + * @return array */ public function getCache() : array { return $this->cache; } + /** + * Return the deprecated + * + * @return array + */ + public function getDeprecated() : array + { + return $this->deprecated; + } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php index 60191b69be47f..dea50316aadeb 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php @@ -46,7 +46,7 @@ public function createFromConfigData( $isList = false; //check if type ends with [] - if ($fieldType{strlen($fieldType) - 2} == '[' && $fieldType{strlen($fieldType) - 1} == ']') { + if ($fieldType[strlen($fieldType) - 2] == '[' && $fieldType[strlen($fieldType) - 1] == ']') { $isList = true; $fieldData['type'] = str_replace('[]', '', $fieldData['type']); $fieldData['itemType'] = str_replace('[]', '', $fieldData['type']); @@ -62,8 +62,9 @@ public function createFromConfigData( 'itemType' => isset($fieldData['itemType']) ? $fieldData['itemType'] : '', 'resolver' => isset($fieldData['resolver']) ? $fieldData['resolver'] : '', 'description' => isset($fieldData['description']) ? $fieldData['description'] : '', - 'cache' => isset($fieldData['cache']) ? $fieldData['cache'] : [], 'arguments' => $arguments, + 'cache' => isset($fieldData['cache']) ? $fieldData['cache'] : [], + 'deprecated' => isset($fieldData['deprecated']) ? $fieldData['deprecated'] : [], ] ); } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php index 8e86f701672c6..1dc8f5a2e779a 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php @@ -27,19 +27,27 @@ class Input implements TypeInterface */ private $description; + /** + * @var array + */ + private $deprecated; + /** * @param string $name * @param Field[] $fields * @param string $description + * @param array $deprecated */ public function __construct( string $name, array $fields, - string $description + string $description, + array $deprecated = [] ) { $this->name = $name; $this->fields = $fields; $this->description = $description; + $this->deprecated = $deprecated; } /** @@ -71,4 +79,14 @@ public function getDescription(): string { return $this->description; } + + /** + * Return the deprecated + * + * @return array + */ + public function getDeprecated() : array + { + return $this->deprecated; + } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php index 0e7ccb831a5a4..a23e83684d43c 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php @@ -72,7 +72,8 @@ private function create( [ 'name' => $typeData['name'], 'fields' => $fields, - 'description' => isset($typeData['description']) ? $typeData['description'] : '' + 'description' => isset($typeData['description']) ? $typeData['description'] : '', + 'deprecated' => isset($typeData['deprecated']) ? $typeData['deprecated'] : [] ] ); } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php index 6080fd0dd73e2..beb4b5a311c94 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php @@ -22,12 +22,13 @@ public function __construct(EnumElement $configElement) { $config = [ 'name' => $configElement->getName(), - 'description' => $configElement->getDescription(), + 'description' => $configElement->getDescription() ]; foreach ($configElement->getValues() as $value) { $config['values'][$value->getValue()] = [ 'value' => $value->getValue(), - 'description' => $value->getDescription() + 'description' => $value->getDescription(), + 'deprecationReason'=> $value->getDeprecatedReason() ]; } parent::__construct($config); diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php index 034a5702090d9..e3f0945cb8dfd 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php @@ -142,6 +142,12 @@ private function getFieldConfig( $fieldConfig['description'] = $field->getDescription(); } + if (!empty($field->getDeprecated())) { + if (isset($field->getDeprecated()['reason'])) { + $fieldConfig['deprecationReason'] = $field->getDeprecated()['reason']; + } + } + if ($field->getResolver() != null) { /** @var ResolverInterface $resolver */ $resolver = $this->objectManager->get($field->getResolver()); diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php new file mode 100644 index 0000000000000..dbbe83bf9fd43 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader; + +/** + * Reads documentation from the annotation "@deprecated" of an AST node + */ +class DeprecatedAnnotationReader +{ + /** + * Read deprecated annotation for a specific node if exists + * + * @param \GraphQL\Language\AST\NodeList $directives + * @return array + */ + public function read(\GraphQL\Language\AST\NodeList $directives) : array + { + $argMap = []; + foreach ($directives as $directive) { + if ($directive->name->value == 'deprecated') { + foreach ($directive->arguments as $directiveArgument) { + if ($directiveArgument->name->value == 'reason') { + $argMap = ["reason" => $directiveArgument->value->value]; + } + } + } + } + return $argMap; + } +} diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php new file mode 100644 index 0000000000000..1c9795ec77cbd --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader; + +/** + * Reads documentation from the annotation "@deprecated" of an AST node + */ +class DeprecatedEnumAnnotationReader +{ + /** + * Read deprecated annotation for a specific node if exists + * + * @param \GraphQL\Language\AST\NodeList $directives + * @return array + */ + public function read(\GraphQL\Language\AST\NodeList $directives) : string + { + foreach ($directives as $directive) { + if ($directive->name->value == 'deprecated') { + foreach ($directive->arguments as $directiveArgument) { + if ($directiveArgument->name->value == 'deprecationReason') { + return $directiveArgument->value->value; + } + } + } + } + return ''; + } +} diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index 7438a4e3da932..2c8ac12347a9a 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -27,20 +27,29 @@ class FieldMetaReader */ private $cacheAnnotationReader; + /** + * @var DeprecatedAnnotationReader + */ + private $deprecatedAnnotationReader; + /** * @param TypeMetaWrapperReader $typeMetaReader * @param DocReader $docReader * @param CacheAnnotationReader|null $cacheAnnotationReader + * @param DeprecatedAnnotationReader|null $deprecatedAnnotationReader */ public function __construct( TypeMetaWrapperReader $typeMetaReader, DocReader $docReader, - CacheAnnotationReader $cacheAnnotationReader = null + CacheAnnotationReader $cacheAnnotationReader = null, + DeprecatedAnnotationReader $deprecatedAnnotationReader = null ) { $this->typeMetaReader = $typeMetaReader; $this->docReader = $docReader; - $this->cacheAnnotationReader = $cacheAnnotationReader ?? \Magento\Framework\App\ObjectManager::getInstance() - ->get(CacheAnnotationReader::class); + $this->cacheAnnotationReader = $cacheAnnotationReader + ?? \Magento\Framework\App\ObjectManager::getInstance()->get(CacheAnnotationReader::class); + $this->deprecatedAnnotationReader = $deprecatedAnnotationReader + ?? \Magento\Framework\App\ObjectManager::getInstance()->get(DeprecatedAnnotationReader::class); } /** @@ -72,10 +81,14 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra $result['description'] = $this->docReader->read($fieldMeta->astNode->directives); } - if ($this->docReader->read($fieldMeta->astNode->directives)) { + if ($this->cacheAnnotationReader->read($fieldMeta->astNode->directives)) { $result['cache'] = $this->cacheAnnotationReader->read($fieldMeta->astNode->directives); } + if ($this->deprecatedAnnotationReader->read($fieldMeta->astNode->directives)) { + $result['deprecated'] = $this->deprecatedAnnotationReader->read($fieldMeta->astNode->directives); + } + $arguments = $fieldMeta->args; foreach ($arguments as $argumentMeta) { $argumentName = $argumentMeta->name; @@ -95,6 +108,11 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra $result['arguments'][$argumentName]['description'] = $this->docReader->read($argumentMeta->astNode->directives); } + + if ($this->deprecatedAnnotationReader->read($argumentMeta->astNode->directives)) { + $result['arguments'][$argumentName]['deprecated'] = + $this->deprecatedAnnotationReader->read($argumentMeta->astNode->directives); + } } return $result; } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php index 3e9e819078db8..a6a629d581b4a 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php @@ -9,6 +9,7 @@ use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DeprecatedEnumAnnotationReader; /** * Composite configuration reader to handle the enum type meta @@ -20,16 +21,25 @@ class EnumType implements TypeMetaReaderInterface */ private $docReader; + /** + * @var DeprecatedEnumAnnotationReader + */ + private $deprecatedEnumAnnotationReader; + /** * @param DocReader $docReader + * @param DeprecatedEnumAnnotationReader $deprecatedEnumAnnotationReader */ - public function __construct(DocReader $docReader) - { + public function __construct( + DocReader $docReader, + DeprecatedEnumAnnotationReader $deprecatedEnumAnnotationReader + ) { $this->docReader = $docReader; + $this->deprecatedEnumAnnotationReader = $deprecatedEnumAnnotationReader; } /** - * {@inheritdoc} + * @inheritdoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -42,13 +52,22 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array foreach ($typeMeta->getValues() as $enumValueMeta) { $result['items'][$enumValueMeta->value] = [ 'name' => strtolower($enumValueMeta->name), - '_value' => $enumValueMeta->value + '_value' => $enumValueMeta->value, + 'description' => $enumValueMeta->description, + 'deprecationReason' =>$enumValueMeta->deprecationReason ]; if ($this->docReader->read($enumValueMeta->astNode->directives)) { $result['items'][$enumValueMeta->value]['description'] = $this->docReader->read($enumValueMeta->astNode->directives); } + + if (!empty($enumValueMeta->deprecationReason) && + $this->deprecatedEnumAnnotationReader->read($enumValueMeta->astNode->directives) + ) { + $result['items'][$enumValueMeta->value]['deprecationReason'] = + $this->deprecatedEnumAnnotationReader->read($enumValueMeta->astNode->directives); + } } if ($this->docReader->read($typeMeta->astNode->directives)) { @@ -56,6 +75,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array } return $result; + } else { return []; } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php index 7614c4954091d..ba8e46dd60557 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php @@ -12,6 +12,7 @@ use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\ImplementsReader; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\CacheAnnotationReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DeprecatedAnnotationReader; /** * Composite configuration reader to handle the object type meta @@ -38,24 +39,33 @@ class ObjectType implements TypeMetaReaderInterface */ private $cacheAnnotationReader; + /** + * @var DeprecatedAnnotationReader + */ + private $deprecatedAnnotationReader; + /** * ObjectType constructor. * @param FieldMetaReader $fieldMetaReader * @param DocReader $docReader * @param ImplementsReader $implementsAnnotation * @param CacheAnnotationReader|null $cacheAnnotationReader + * @param DeprecatedAnnotationReader|null $deprecatedAnnotationReader */ public function __construct( FieldMetaReader $fieldMetaReader, DocReader $docReader, ImplementsReader $implementsAnnotation, - CacheAnnotationReader $cacheAnnotationReader = null + CacheAnnotationReader $cacheAnnotationReader = null, + DeprecatedAnnotationReader $deprecatedAnnotationReader = null ) { $this->fieldMetaReader = $fieldMetaReader; $this->docReader = $docReader; $this->implementsAnnotation = $implementsAnnotation; $this->cacheAnnotationReader = $cacheAnnotationReader ?? \Magento\Framework\App\ObjectManager::getInstance() ->get(CacheAnnotationReader::class); + $this->deprecatedAnnotationReader = $deprecatedAnnotationReader + ?? \Magento\Framework\App\ObjectManager::getInstance()->get(DeprecatedAnnotationReader::class); } /** @@ -85,13 +95,17 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array } if ($this->docReader->read($typeMeta->astNode->directives)) { - $result['description'] = $this->docReader->read($typeMeta->astNode->directives); + $result['description'] = $this->docReader->read($typeMeta->astNode->directives); } - if ($this->docReader->read($typeMeta->astNode->directives)) { + if ($this->cacheAnnotationReader->read($typeMeta->astNode->directives)) { $result['cache'] = $this->cacheAnnotationReader->read($typeMeta->astNode->directives); } + if ($this->deprecatedAnnotationReader->read($typeMeta->astNode->directives)) { + $result['deprecated'] = $this->deprecatedAnnotationReader->read($typeMeta->astNode->directives); + } + return $result; } else { return []; From a249557376f33273eaaad29c4e14718b5a4377b2 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 20 Aug 2019 15:37:23 -0500 Subject: [PATCH 301/841] MC-18512: Dynamically inject all searchable custom attributes for product filtering --- app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php | 6 +----- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 6a17f730c0f1a..3bc61a0fb3f2c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -98,11 +98,7 @@ public function resolve( //get product children fields queried $productFields = (array)$info->getFieldSelection(1); - $searchCriteria = $this->searchApiCriteriaBuilder->build( - $args, - isset($productFields['filters']) - ); - + $searchCriteria = $this->searchApiCriteriaBuilder->build($args, isset($productFields['filters'])); $searchResult = $this->searchQuery->getResult($searchCriteria, $info, $args); if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) { diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index e6daf4f4d753e..aadd43fafbd7f 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -221,7 +221,7 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model products( pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."), currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), - sort: ProductSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") + sort: ProductAttributeSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") ): CategoryProducts @doc(description: "The list of products assigned to the category.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products") breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs") } From 8d765a32dd7480b3a17d9745aeb2ee6d7f80bbe2 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 20 Aug 2019 16:53:20 -0500 Subject: [PATCH 302/841] MC-19441: Fix web api test failures - fixed 3 tests --- .../GraphQl/Catalog/ProductSearchTest.php | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 3284a4a7172fe..bcd0ffedf542f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -717,6 +717,7 @@ public function testFilterVisibleProductsWithMatchingSkuOrNameWithSpecialPrice() public function testSearchWithFilterWithPageSizeEqualTotalCount() { + CacheCleaner::cleanAll(); $query = <<<QUERY { @@ -724,14 +725,7 @@ public function testSearchWithFilterWithPageSizeEqualTotalCount() search : "simple" filter: { - special_price:{neq:"null"} - price:{lt:"60"} - or: - { - sku:{like:"%simple%"} - name:{like:"%configurable%"} - } - weight:{eq:"1"} + price:{from:"60"} } pageSize:2 currentPage:2 @@ -872,13 +866,16 @@ public function testQueryProductsInCurrentPageSortedByPriceASC() */ public function testQueryProductsSortedByNameASC() { + CacheCleaner::cleanAll(); $query = <<<QUERY { products( filter: { - sku:{in:["simple2", "simple1"]} + sku: { + like:"simple%" + } } pageSize:1 currentPage:2 @@ -1058,18 +1055,14 @@ public function testFilterProductsBySingleCategoryId() */ public function testQuerySortByPriceDESCWithDefaultPageSize() { + CacheCleaner::cleanAll(); $query = <<<QUERY { products( filter: { - price:{gt: "5", lt: "60"} - or: - { - sku:{like:"%simple%"} - name:{like:"%Configurable%"} - } + sku:{like:"%simple%"} } sort: { @@ -1131,9 +1124,6 @@ public function testProductQueryUsingFromAndToFilterInput() from:"5" to:"20" } } - sort: { - sku: DESC - } ) { total_count items { From ecf97bce95c9862fecd5349cef06738861b60d32 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Tue, 20 Aug 2019 20:25:27 -0400 Subject: [PATCH 303/841] Introduce mock for UPS shipping testing Fixes magento/graphql-ce#815 --- .../Magento/TestModuleUps/Model/Carrier.php | 111 ++++++++++++++++++ .../Model/MockResponseBodyLoader.php | 63 ++++++++++ .../TestModuleUps/_files/mock_response_ca.txt | 5 + .../TestModuleUps/_files/mock_response_us.txt | 5 + .../_files/Magento/TestModuleUps/etc/di.xml | 10 ++ .../Magento/TestModuleUps/etc/module.xml | 14 +++ .../Magento/TestModuleUps/registration.php | 13 ++ 7 files changed, 221 insertions(+) create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleUps/Model/Carrier.php create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleUps/Model/MockResponseBodyLoader.php create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_ca.txt create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_us.txt create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleUps/etc/di.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleUps/etc/module.xml create mode 100644 dev/tests/api-functional/_files/Magento/TestModuleUps/registration.php diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/Carrier.php b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/Carrier.php new file mode 100644 index 0000000000000..b3c3c124cfe47 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/Carrier.php @@ -0,0 +1,111 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestModuleUps\Model; + +use Magento\Framework\Async\ProxyDeferredFactory; +use Magento\Framework\HTTP\AsyncClientInterface; +use Magento\Framework\HTTP\ClientFactory; +use Magento\Framework\Xml\Security; +use Magento\Ups\Helper\Config; + +/** + * Mock UPS shipping implementation + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Carrier extends \Magento\Ups\Model\Carrier +{ + /** + * @var MockResponseBodyLoader + */ + private $mockResponseLoader; + + /** + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory + * @param \Psr\Log\LoggerInterface $logger + * @param Security $xmlSecurity + * @param \Magento\Shipping\Model\Simplexml\ElementFactory $xmlElFactory + * @param \Magento\Shipping\Model\Rate\ResultFactory $rateFactory + * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory + * @param \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory + * @param \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory + * @param \Magento\Shipping\Model\Tracking\Result\StatusFactory $trackStatusFactory + * @param \Magento\Directory\Model\RegionFactory $regionFactory + * @param \Magento\Directory\Model\CountryFactory $countryFactory + * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory + * @param \Magento\Directory\Helper\Data $directoryData + * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry + * @param \Magento\Framework\Locale\FormatInterface $localeFormat + * @param Config $configHelper + * @param ClientFactory $httpClientFactory + * @param array $data + * @param AsyncClientInterface $asyncHttpClient + * @param ProxyDeferredFactory $proxyDeferredFactory + * @param MockResponseBodyLoader $mockResponseLoader + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory, + \Psr\Log\LoggerInterface $logger, + Security $xmlSecurity, + \Magento\Shipping\Model\Simplexml\ElementFactory $xmlElFactory, + \Magento\Shipping\Model\Rate\ResultFactory $rateFactory, + \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory, + \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory, + \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory, + \Magento\Shipping\Model\Tracking\Result\StatusFactory $trackStatusFactory, + \Magento\Directory\Model\RegionFactory $regionFactory, + \Magento\Directory\Model\CountryFactory $countryFactory, + \Magento\Directory\Model\CurrencyFactory $currencyFactory, + \Magento\Directory\Helper\Data $directoryData, + \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, + \Magento\Framework\Locale\FormatInterface $localeFormat, + Config $configHelper, + ClientFactory $httpClientFactory, + AsyncClientInterface $asyncHttpClient, + ProxyDeferredFactory $proxyDeferredFactory, + MockResponseBodyLoader $mockResponseLoader, + array $data = [] + ) { + parent::__construct( + $scopeConfig, + $rateErrorFactory, + $logger, + $xmlSecurity, + $xmlElFactory, + $rateFactory, + $rateMethodFactory, + $trackFactory, + $trackErrorFactory, + $trackStatusFactory, + $regionFactory, + $countryFactory, + $currencyFactory, + $directoryData, + $stockRegistry, + $localeFormat, + $configHelper, + $httpClientFactory, + $data, + $asyncHttpClient, + $proxyDeferredFactory + ); + $this->mockResponseLoader = $mockResponseLoader; + } + + /** + * @inheritdoc + */ + protected function _getCgiQuotes() + { + $responseBody = $this->mockResponseLoader->loadForRequest($this->_rawRequest->getDestCountry()); + return $this->_parseCgiResponse($responseBody); + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/MockResponseBodyLoader.php b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/MockResponseBodyLoader.php new file mode 100644 index 0000000000000..fe1750fa648f3 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/MockResponseBodyLoader.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestModuleUps\Model; + +use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Module\Dir; +use Magento\Framework\Filesystem\Io\File; + +/** + * Load mock response body for UPS rate request + */ +class MockResponseBodyLoader +{ + private const RESPONSE_FILE_PATTERN = '%s/_files/mock_response_%s.txt'; + + /** + * @var Dir + */ + private $moduleDirectory; + + /** + * @var File + */ + private $fileIo; + + /** + * @param Dir $moduleDirectory + * @param File $fileIo + */ + public function __construct( + Dir $moduleDirectory, + File $fileIo + ) { + $this->moduleDirectory = $moduleDirectory; + $this->fileIo = $fileIo; + } + + /** + * Loads mock cgi response body for a given country + * + * @param string $country + * @return string + * @throws NotFoundException + */ + public function loadForRequest(string $country): string + { + $country = strtolower($country); + $moduleDir = $this->moduleDirectory->getDir('Magento_TestModuleUps'); + + $responsePath = sprintf(static::RESPONSE_FILE_PATTERN, $moduleDir, $country); + + if (!$this->fileIo->fileExists($responsePath)) { + throw new NotFoundException(__('%1 is not a valid destination country.', $country)); + } + + return $this->fileIo->read($responsePath); + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_ca.txt b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_ca.txt new file mode 100644 index 0000000000000..eca3e47a7e138 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_ca.txt @@ -0,0 +1,5 @@ +UPSOnLine4%XDM%90034%US%M4L 1V3%CA%081%1%138.17%0.00%138.17%-1% +4%XPR%90034%US%M4L 1V3%CA%081%1%95.07%0.00%95.07%12:00 P.M.% +4%WXS%90034%US%M4L 1V3%CA%481%1%93.99%0.00%93.99%-1% +4%XPD%90034%US%M4L 1V3%CA%071%1%85.85%0.00%85.85%-1% +4%STD%90034%US%M4L 1V3%CA%053%1%27.08%0.00%27.08%-1% \ No newline at end of file diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_us.txt b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_us.txt new file mode 100644 index 0000000000000..56f73dbd93a5a --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_us.txt @@ -0,0 +1,5 @@ +UPSOnLine4%1DM%90034%US%75477%US%106%1%112.44%0.00%112.44%12:00 P.M.% +4%1DA%90034%US%75477%US%106%1%80.42%0.00%80.42%End of Day% +4%2DA%90034%US%75477%US%206%1%39.05%0.00%39.05%End of Day% +4%3DS%90034%US%75477%US%306%1%31.69%0.00%31.69%End of Day% +4%GND%90034%US%75477%US%006%1%15.61%0.00%15.61%End of Day% \ No newline at end of file diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/di.xml new file mode 100644 index 0000000000000..28c2fa8e4d45f --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\Ups\Model\Carrier" type="Magento\TestModuleUps\Model\Carrier"/> +</config> diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/module.xml new file mode 100644 index 0000000000000..77d1d15f78d7d --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/module.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_TestModuleUps"> + <sequence> + <module name="Magento_Ups"/> + </sequence> + </module> +</config> diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/registration.php b/dev/tests/api-functional/_files/Magento/TestModuleUps/registration.php new file mode 100644 index 0000000000000..0668a2522e874 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/registration.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +$registrar = new ComponentRegistrar(); +if ($registrar->getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleUps') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleUps', __DIR__); +} From 6f66bedeac833e6b3c73aae602cfa6487eaf72b3 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Tue, 20 Aug 2019 21:23:25 -0400 Subject: [PATCH 304/841] Add summary_count to cart response object --- .../Model/Resolver/CartSummaryCount.php | 34 ++++++++++ .../Magento/QuoteGraphQl/etc/schema.graphqls | 1 + .../GraphQl/Quote/GetCartSummaryCountTest.php | 64 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 app/code/Magento/QuoteGraphQl/Model/Resolver/CartSummaryCount.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartSummaryCountTest.php diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartSummaryCount.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartSummaryCount.php new file mode 100644 index 0000000000000..6e06241ebe6c1 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartSummaryCount.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class CartSummaryCount implements ResolverInterface +{ + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + /** @var Quote $cart */ + $cart = $value['model']; + + return $cart->getItemsSummaryQty(); + } +} diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 6d7f4daf40ded..f9e05f9b86084 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -197,6 +197,7 @@ type Cart { available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods") selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod") prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices") + summary_count: Int! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartSummaryCount") } interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartSummaryCountTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartSummaryCountTest.php new file mode 100644 index 0000000000000..2017ef734f8a6 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartSummaryCountTest.php @@ -0,0 +1,64 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for getting summary count from cart query + */ +class GetCartSummaryCountTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetNewBillingAddress() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey('cart', $response); + $cart = $response['cart']; + self::assertArrayHasKey('summary_count', $cart); + self::assertEquals(2, $cart['summary_count']); + } + + /** + * Create cart query + * + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id: "{$maskedQuoteId}") { + summary_count + } +} +QUERY; + } +} From f30bb79d4ca6634c2ecfa9d0c78fa4896072d97c Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Tue, 20 Aug 2019 22:47:18 -0400 Subject: [PATCH 305/841] Upgrade graphql-php to v0.13.6 Follows upgrade path outlined in https://github.com/webonyx/graphql-php/blob/master/UPGRADE.md --- composer.json | 2 +- composer.lock | 45 +++++++++++-------- .../Schema/Type/ResolveInfoFactory.php | 18 +++++++- .../GraphQl/Schema/Type/ScalarTypes.php | 8 +++- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 293cb06ef403c..d90eadffb9db5 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/process": "~4.1.0|~4.2.0|~4.3.0", "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", - "webonyx/graphql-php": "^0.12.6", + "webonyx/graphql-php": "^0.13.6", "zendframework/zend-captcha": "^2.7.1", "zendframework/zend-code": "~3.3.0", "zendframework/zend-config": "^2.6.0", diff --git a/composer.lock b/composer.lock index f67eb50675314..d4348362255ab 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a4299e3f4f0d4dd4915f37a5dde8e2ed", + "content-hash": "6fd70adf831929273a251803fa361cd4", "packages": [ { "name": "braintree/braintree_php", @@ -1552,28 +1552,28 @@ "authors": [ { "name": "Jim Wigginton", - "role": "Lead Developer", - "email": "terrafrost@php.net" + "email": "terrafrost@php.net", + "role": "Lead Developer" }, { "name": "Patrick Monnerat", - "role": "Developer", - "email": "pm@datasphere.ch" + "email": "pm@datasphere.ch", + "role": "Developer" }, { "name": "Andreas Fischer", - "role": "Developer", - "email": "bantu@phpbb.com" + "email": "bantu@phpbb.com", + "role": "Developer" }, { "name": "Hans-Jürgen Petrich", - "role": "Developer", - "email": "petrich@tronic-media.com" + "email": "petrich@tronic-media.com", + "role": "Developer" }, { "name": "Graham Campbell", - "role": "Developer", - "email": "graham@alt-three.com" + "email": "graham@alt-three.com", + "role": "Developer" } ], "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", @@ -2402,7 +2402,7 @@ }, { "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "email": "backendtea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -2670,24 +2670,31 @@ }, { "name": "webonyx/graphql-php", - "version": "v0.12.6", + "version": "v0.13.6", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "4c545e5ec4fc37f6eb36c19f5a0e7feaf5979c95" + "reference": "123af49e46d26b0cd2e7a71a387253aa01ea9a6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/4c545e5ec4fc37f6eb36c19f5a0e7feaf5979c95", - "reference": "4c545e5ec4fc37f6eb36c19f5a0e7feaf5979c95", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/123af49e46d26b0cd2e7a71a387253aa01ea9a6b", + "reference": "123af49e46d26b0cd2e7a71a387253aa01ea9a6b", "shasum": "" }, "require": { + "ext-json": "*", "ext-mbstring": "*", - "php": ">=5.6" + "php": "^7.1||^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8", + "doctrine/coding-standard": "^6.0", + "phpbench/phpbench": "^0.14.0", + "phpstan/phpstan": "^0.11.4", + "phpstan/phpstan-phpunit": "^0.11.0", + "phpstan/phpstan-strict-rules": "^0.11.0", + "phpunit/phpcov": "^5.0", + "phpunit/phpunit": "^7.2", "psr/http-message": "^1.0", "react/promise": "2.*" }, @@ -2711,7 +2718,7 @@ "api", "graphql" ], - "time": "2018-09-02T14:59:54+00:00" + "time": "2019-08-07T08:16:55+00:00" }, { "name": "wikimedia/less.php", diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/ResolveInfoFactory.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/ResolveInfoFactory.php index 1dde923bb2a98..335b991f693c8 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/ResolveInfoFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/ResolveInfoFactory.php @@ -7,11 +7,27 @@ namespace Magento\Framework\GraphQl\Schema\Type; +use Magento\Framework\ObjectManagerInterface; + /** * Factory for wrapper of GraphQl ResolveInfo */ class ResolveInfoFactory { + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + /** * Create a wrapper resolver info from the instance of the library object * @@ -25,6 +41,6 @@ public function create(\GraphQL\Type\Definition\ResolveInfo $info) : ResolveInfo $values[$key] = $value; } - return new ResolveInfo($values); + return $this->objectManager->create(ResolveInfo::class, $values); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php index 508cada1a6958..04b9354855ea9 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php @@ -13,23 +13,27 @@ class ScalarTypes { /** + * Check if type is scalar + * * @param string $typeName * @return bool */ public function isScalarType(string $typeName) : bool { - $internalTypes = \GraphQL\Type\Definition\Type::getInternalTypes(); + $internalTypes = \GraphQL\Type\Definition\Type::getStandardTypes(); return isset($internalTypes[$typeName]) ? true : false; } /** + * Get instance of scalar type + * * @param string $typeName * @return \GraphQL\Type\Definition\ScalarType|\GraphQL\Type\Definition\Type * @throws \LogicException */ public function getScalarTypeInstance(string $typeName) : \GraphQL\Type\Definition\Type { - $internalTypes = \GraphQL\Type\Definition\Type::getInternalTypes(); + $internalTypes = \GraphQL\Type\Definition\Type::getStandardTypes(); if ($this->isScalarType($typeName)) { return $internalTypes[$typeName]; } else { From 38f729bdaa9ce619a6cd17f0702fd8897106f4da Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Wed, 21 Aug 2019 06:37:44 +0000 Subject: [PATCH 306/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProduct.php | 51 ++++--- .../Indexer/ReindexRuleProductPriceTest.php | 58 +++----- .../Model/Indexer/ReindexRuleProductTest.php | 135 +++++++++++------- 3 files changed, 139 insertions(+), 105 deletions(-) diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index 55a234bb8ae27..e589c8595ce2c 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -8,7 +8,10 @@ use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper; use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; -use Magento\Framework\App\ObjectManager; +use Magento\CatalogRule\Model\Rule; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\ScopeInterface; /** * Reindex rule relations with products. @@ -16,7 +19,7 @@ class ReindexRuleProduct { /** - * @var \Magento\Framework\App\ResourceConnection + * @var ResourceConnection */ private $resource; @@ -31,36 +34,40 @@ class ReindexRuleProduct private $tableSwapper; /** - * @param \Magento\Framework\App\ResourceConnection $resource + * @var TimezoneInterface + */ + private $localeDate; + + /** + * @param ResourceConnection $resource * @param ActiveTableSwitcher $activeTableSwitcher - * @param TableSwapper|null $tableSwapper + * @param TableSwapper $tableSwapper + * @param TimezoneInterface $localeDate */ public function __construct( - \Magento\Framework\App\ResourceConnection $resource, + ResourceConnection $resource, ActiveTableSwitcher $activeTableSwitcher, - TableSwapper $tableSwapper = null + TableSwapper $tableSwapper, + TimezoneInterface $localeDate ) { $this->resource = $resource; $this->activeTableSwitcher = $activeTableSwitcher; - $this->tableSwapper = $tableSwapper ?? - ObjectManager::getInstance()->get(TableSwapper::class); + $this->tableSwapper = $tableSwapper; + $this->localeDate = $localeDate; } /** * Reindex information about rule relations with products. * - * @param \Magento\CatalogRule\Model\Rule $rule + * @param Rule $rule * @param int $batchCount * @param bool $useAdditionalTable * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function execute( - \Magento\CatalogRule\Model\Rule $rule, - $batchCount, - $useAdditionalTable = false - ) { + public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) + { if (!$rule->getIsActive() || empty($rule->getWebsiteIds())) { return false; } @@ -84,21 +91,26 @@ public function execute( $ruleId = $rule->getId(); $customerGroupIds = $rule->getCustomerGroupIds(); - $fromTime = strtotime($rule->getFromDate()); - $toTime = strtotime($rule->getToDate()); - $toTime = $toTime ? $toTime + \Magento\CatalogRule\Model\Indexer\IndexBuilder::SECONDS_IN_DAY - 1 : 0; $sortOrder = (int)$rule->getSortOrder(); $actionOperator = $rule->getSimpleAction(); $actionAmount = $rule->getDiscountAmount(); $actionStop = $rule->getStopRulesProcessing(); $rows = []; + foreach ($websiteIds as $websiteId) { + $scopeTz = new \DateTimeZone( + $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId) + ); + $fromTime = (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp(); + $toTime = $rule->getToDate() + ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1 + : 0; - foreach ($productIds as $productId => $validationByWebsite) { - foreach ($websiteIds as $websiteId) { + foreach ($productIds as $productId => $validationByWebsite) { if (empty($validationByWebsite[$websiteId])) { continue; } + foreach ($customerGroupIds as $customerGroupId) { $rows[] = [ 'rule_id' => $ruleId, @@ -123,6 +135,7 @@ public function execute( if (!empty($rows)) { $connection->insertMultiple($indexTable, $rows); } + return true; } } diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php index d838d195acd28..5f63283df6760 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php @@ -11,7 +11,6 @@ use Magento\CatalogRule\Model\Indexer\ReindexRuleProductPrice; use Magento\CatalogRule\Model\Indexer\RuleProductPricesPersistor; use Magento\CatalogRule\Model\Indexer\RuleProductsSelectBuilder; -use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Store\Api\Data\GroupInterface; use Magento\Store\Api\Data\WebsiteInterface; @@ -41,45 +40,37 @@ class ReindexRuleProductPriceTest extends \PHPUnit\Framework\TestCase private $productPriceCalculatorMock; /** - * @var DateTime|MockObject + * @var TimezoneInterface|MockObject */ - private $dateTimeMock; + private $localeDate; /** * @var RuleProductPricesPersistor|MockObject */ private $pricesPersistorMock; - /** - * @var TimezoneInterface|MockObject - */ - private $localeDate; - protected function setUp() { $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); $this->ruleProductsSelectBuilderMock = $this->createMock(RuleProductsSelectBuilder::class); $this->productPriceCalculatorMock = $this->createMock(ProductPriceCalculator::class); - $this->dateTimeMock = $this->createMock(DateTime::class); - $this->pricesPersistorMock = $this->createMock(RuleProductPricesPersistor::class); $this->localeDate = $this->createMock(TimezoneInterface::class); + $this->pricesPersistorMock = $this->createMock(RuleProductPricesPersistor::class); $this->model = new ReindexRuleProductPrice( $this->storeManagerMock, $this->ruleProductsSelectBuilderMock, $this->productPriceCalculatorMock, - $this->dateTimeMock, - $this->pricesPersistorMock, - $this->localeDate + $this->localeDate, + $this->pricesPersistorMock ); } public function testExecute() { $websiteId = 234; - $storeGroupId = 30; - $storeId = 40; - $productMock = $this->createMock(Product::class); + $defaultGroupId = 11; + $defaultStoreId = 22; $websiteMock = $this->createMock(WebsiteInterface::class); $websiteMock->expects($this->once()) @@ -87,19 +78,22 @@ public function testExecute() ->willReturn($websiteId); $websiteMock->expects($this->once()) ->method('getDefaultGroupId') - ->willReturn($storeGroupId); + ->willReturn($defaultGroupId); $this->storeManagerMock->expects($this->once()) ->method('getWebsites') ->willReturn([$websiteMock]); - $storeGroupMock = $this->createMock(GroupInterface::class); - $storeGroupMock->expects($this->once()) + $groupMock = $this->createMock(GroupInterface::class); + $groupMock->method('getId') + ->willReturn($defaultStoreId); + $groupMock->expects($this->once()) ->method('getDefaultStoreId') - ->willReturn($storeId); + ->willReturn($defaultStoreId); $this->storeManagerMock->expects($this->once()) ->method('getGroup') - ->with($storeGroupId) - ->willReturn($storeGroupMock); + ->with($defaultGroupId) + ->willReturn($groupMock); + $productMock = $this->createMock(Product::class); $statementMock = $this->createMock(\Zend_Db_Statement_Interface::class); $this->ruleProductsSelectBuilderMock->expects($this->once()) ->method('build') @@ -115,22 +109,10 @@ public function testExecute() 'action_stop' => true ]; - $this->dateTimeMock->expects($this->at(0)) - ->method('date') - ->with('Y-m-d 00:00:00', $ruleData['from_time']) - ->willReturn($ruleData['from_time']); - $this->dateTimeMock->expects($this->at(1)) - ->method('timestamp') - ->with($ruleData['from_time']) - ->willReturn($ruleData['from_time']); - $this->dateTimeMock->expects($this->at(2)) - ->method('date') - ->with('Y-m-d 00:00:00', $ruleData['to_time']) - ->willReturn($ruleData['to_time']); - $this->dateTimeMock->expects($this->at(3)) - ->method('timestamp') - ->with($ruleData['to_time']) - ->willReturn($ruleData['to_time']); + $this->localeDate->expects($this->once()) + ->method('scopeDate') + ->with($defaultStoreId, null, true) + ->willReturn(new \DateTime()); $statementMock->expects($this->at(0)) ->method('fetch') diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php index 0dbbaee8d2871..ff566fa3cc774 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php @@ -8,89 +8,107 @@ use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher; use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface; +use Magento\CatalogRule\Model\Indexer\ReindexRuleProduct; +use Magento\CatalogRule\Model\Rule; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\MockObject\MockObject; class ReindexRuleProductTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct + * @var ReindexRuleProduct */ private $model; /** - * @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + * @var ResourceConnection|MockObject */ private $resourceMock; /** - * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject + * @var ActiveTableSwitcher|MockObject */ private $activeTableSwitcherMock; /** - * @var IndexerTableSwapperInterface|\PHPUnit_Framework_MockObject_MockObject + * @var IndexerTableSwapperInterface|MockObject */ private $tableSwapperMock; + /** + * @var TimezoneInterface|MockObject + */ + private $localeDateMock; + protected function setUp() { - $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->activeTableSwitcherMock = $this->getMockBuilder(ActiveTableSwitcher::class) - ->disableOriginalConstructor() - ->getMock(); - $this->tableSwapperMock = $this->getMockForAbstractClass( - IndexerTableSwapperInterface::class - ); - $this->model = new \Magento\CatalogRule\Model\Indexer\ReindexRuleProduct( + $this->resourceMock = $this->createMock(ResourceConnection::class); + $this->activeTableSwitcherMock = $this->createMock(ActiveTableSwitcher::class); + $this->tableSwapperMock = $this->createMock(IndexerTableSwapperInterface::class); + $this->localeDateMock = $this->createMock(TimezoneInterface::class); + + $this->model = new ReindexRuleProduct( $this->resourceMock, $this->activeTableSwitcherMock, - $this->tableSwapperMock + $this->tableSwapperMock, + $this->localeDateMock ); } public function testExecuteIfRuleInactive() { - $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class) - ->disableOriginalConstructor() - ->getMock(); - $ruleMock->expects($this->once())->method('getIsActive')->willReturn(false); + $ruleMock = $this->createMock(Rule::class); + $ruleMock->expects($this->once()) + ->method('getIsActive') + ->willReturn(false); $this->assertFalse($this->model->execute($ruleMock, 100, true)); } public function testExecuteIfRuleWithoutWebsiteIds() { - $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class) - ->disableOriginalConstructor() - ->getMock(); - $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true); - $ruleMock->expects($this->once())->method('getWebsiteIds')->willReturn(null); + $ruleMock = $this->createMock(Rule::class); + $ruleMock->expects($this->once()) + ->method('getIsActive') + ->willReturn(true); + $ruleMock->expects($this->once()) + ->method('getWebsiteIds') + ->willReturn(null); $this->assertFalse($this->model->execute($ruleMock, 100, true)); } public function testExecute() { + $websiteId = 3; + $websiteTz = 'America/Los_Angeles'; $productIds = [ - 4 => [1 => 1], - 5 => [1 => 1], - 6 => [1 => 1], + 4 => [$websiteId => 1], + 5 => [$websiteId => 1], + 6 => [$websiteId => 1], ]; - $ruleMock = $this->getMockBuilder(\Magento\CatalogRule\Model\Rule::class) - ->disableOriginalConstructor() - ->getMock(); - $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true); - $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn(1); - $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds); + + $ruleMock = $this->createMock(Rule::class); + $ruleMock->expects($this->once()) + ->method('getIsActive') + ->willReturn(true); + $ruleMock->expects($this->exactly(2)) + ->method('getWebsiteIds') + ->willReturn([$websiteId]); + $ruleMock->expects($this->once()) + ->method('getMatchingProductIds') + ->willReturn($productIds); $this->tableSwapperMock->expects($this->once()) ->method('getWorkingTableName') ->with('catalogrule_product') ->willReturn('catalogrule_product_replica'); - $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resourceMock->expects($this->at(0))->method('getConnection')->willReturn($connectionMock); + $connectionMock = $this->createMock(AdapterInterface::class); + $this->resourceMock->expects($this->at(0)) + ->method('getConnection') + ->willReturn($connectionMock); $this->resourceMock->expects($this->at(1)) ->method('getTableName') ->with('catalogrule_product') @@ -100,21 +118,42 @@ public function testExecute() ->with('catalogrule_product_replica') ->willReturn('catalogrule_product_replica'); - $ruleMock->expects($this->once())->method('getId')->willReturn(100); - $ruleMock->expects($this->once())->method('getCustomerGroupIds')->willReturn([10]); - $ruleMock->expects($this->once())->method('getFromDate')->willReturn('2017-06-21'); - $ruleMock->expects($this->once())->method('getToDate')->willReturn('2017-06-30'); - $ruleMock->expects($this->once())->method('getSortOrder')->willReturn(1); - $ruleMock->expects($this->once())->method('getSimpleAction')->willReturn('simple_action'); - $ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43); - $ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true); + $ruleMock->expects($this->once()) + ->method('getId') + ->willReturn(100); + $ruleMock->expects($this->once()) + ->method('getCustomerGroupIds') + ->willReturn([10]); + $ruleMock->expects($this->atLeastOnce()) + ->method('getFromDate') + ->willReturn('2017-06-21'); + $ruleMock->expects($this->atLeastOnce()) + ->method('getToDate') + ->willReturn('2017-06-30'); + $ruleMock->expects($this->once()) + ->method('getSortOrder') + ->willReturn(1); + $ruleMock->expects($this->once()) + ->method('getSimpleAction') + ->willReturn('simple_action'); + $ruleMock->expects($this->once()) + ->method('getDiscountAmount') + ->willReturn(43); + $ruleMock->expects($this->once()) + ->method('getStopRulesProcessing') + ->willReturn(true); + + $this->localeDateMock->expects($this->once()) + ->method('getConfigTimezone') + ->with(ScopeInterface::SCOPE_WEBSITE, $websiteId) + ->willReturn($websiteTz); $batchRows = [ [ 'rule_id' => 100, 'from_time' => 1498028400, 'to_time' => 1498892399, - 'website_id' => 1, + 'website_id' => $websiteId, 'customer_group_id' => 10, 'product_id' => 4, 'action_operator' => 'simple_action', @@ -126,7 +165,7 @@ public function testExecute() 'rule_id' => 100, 'from_time' => 1498028400, 'to_time' => 1498892399, - 'website_id' => 1, + 'website_id' => $websiteId, 'customer_group_id' => 10, 'product_id' => 5, 'action_operator' => 'simple_action', @@ -141,7 +180,7 @@ public function testExecute() 'rule_id' => 100, 'from_time' => 1498028400, 'to_time' => 1498892399, - 'website_id' => 1, + 'website_id' => $websiteId, 'customer_group_id' => 10, 'product_id' => 6, 'action_operator' => 'simple_action', From d7949280344ed316ded0871b831319c009548b37 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Wed, 21 Aug 2019 15:14:17 +0700 Subject: [PATCH 307/841] Resolve Virtual Quote shouldn't display "Shipping & Handling" total when Create New Order issue24212 --- .../Model/Quote/Address/Total/Shipping.php | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php index e9a63dad6e169..3ce148ee80b8c 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php +++ b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Quote\Model\Quote\Address\Total; use Magento\Framework\Pricing\PriceCurrencyInterface; @@ -112,17 +115,21 @@ public function collect( */ public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address\Total $total) { - $amount = $total->getShippingAmount(); - $shippingDescription = $total->getShippingDescription(); - $title = ($shippingDescription) - ? __('Shipping & Handling (%1)', $shippingDescription) - : __('Shipping & Handling'); + if (!$quote->getIsVirtual()) { + $amount = $total->getShippingAmount(); + $shippingDescription = $total->getShippingDescription(); + $title = ($shippingDescription) + ? __('Shipping & Handling (%1)', $shippingDescription) + : __('Shipping & Handling'); - return [ - 'code' => $this->getCode(), - 'title' => $title, - 'value' => $amount - ]; + return [ + 'code' => $this->getCode(), + 'title' => $title, + 'value' => $amount + ]; + } else { + return []; + } } /** From 4e7388636e8689c67292138026935301a33918c2 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Wed, 21 Aug 2019 15:04:30 +0300 Subject: [PATCH 308/841] magento/magento2#23372: Static test fix. --- lib/internal/Magento/Framework/Phrase/__.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/internal/Magento/Framework/Phrase/__.php b/lib/internal/Magento/Framework/Phrase/__.php index a971df585df2d..0f828acd828b5 100644 --- a/lib/internal/Magento/Framework/Phrase/__.php +++ b/lib/internal/Magento/Framework/Phrase/__.php @@ -7,7 +7,10 @@ /** * Create value-object \Magento\Framework\Phrase + * * @SuppressWarnings(PHPMD.ShortMethodName) + * phpcs:disable Squiz.Functions.GlobalFunction + * @param array $argc * @return \Magento\Framework\Phrase */ function __(...$argc) From a917ee7ee60b891b785dbdced8ee366a6de6c934 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Wed, 21 Aug 2019 11:43:46 +0300 Subject: [PATCH 309/841] magento/magento2#21798: Static test fix. --- .../Test/Unit/Page/Config/RendererTest.php | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php index 7767066e99992..a702ea5458a87 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Page/Config/RendererTest.php @@ -321,12 +321,14 @@ public function testRenderAssets($groupOne, $groupTwo, $expectedResult) ->willReturn($groupAssetsOne); $groupMockOne->expects($this->any()) ->method('getProperty') - ->willReturnMap([ - [GroupedCollection::PROPERTY_CAN_MERGE, true], - [GroupedCollection::PROPERTY_CONTENT_TYPE, $groupOne['type']], - ['attributes', $groupOne['attributes']], - ['ie_condition', $groupOne['condition']], - ]); + ->willReturnMap( + [ + [GroupedCollection::PROPERTY_CAN_MERGE, true], + [GroupedCollection::PROPERTY_CONTENT_TYPE, $groupOne['type']], + ['attributes', $groupOne['attributes']], + ['ie_condition', $groupOne['condition']], + ] + ); $assetMockTwo = $this->createMock(\Magento\Framework\View\Asset\AssetInterface::class); $assetMockTwo->expects($this->once()) @@ -344,12 +346,14 @@ public function testRenderAssets($groupOne, $groupTwo, $expectedResult) ->willReturn($groupAssetsTwo); $groupMockTwo->expects($this->any()) ->method('getProperty') - ->willReturnMap([ - [GroupedCollection::PROPERTY_CAN_MERGE, true], - [GroupedCollection::PROPERTY_CONTENT_TYPE, $groupTwo['type']], - ['attributes', $groupTwo['attributes']], - ['ie_condition', $groupTwo['condition']], - ]); + ->willReturnMap( + [ + [GroupedCollection::PROPERTY_CAN_MERGE, true], + [GroupedCollection::PROPERTY_CONTENT_TYPE, $groupTwo['type']], + ['attributes', $groupTwo['attributes']], + ['ie_condition', $groupTwo['condition']], + ] + ); $this->pageConfigMock->expects($this->once()) ->method('getAssetCollection') From f4c293dc5ba9c175b77e53a25188013d084715a3 Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Fri, 16 Aug 2019 17:41:31 -0500 Subject: [PATCH 310/841] MC-19156: Product images are taken from the wrong cache hash - Fix store view is not properly resolved for media when "Add Store Code to Urls" is enabled - Add store view code to media URL when "Add Store Code to Urls" is enabled --- .../Model/Product/Image/ParamsBuilder.php | 20 ++++++---- .../MediaStorage/Service/ImageResize.php | 38 +++++++++++++------ .../Test/Unit/Service/ImageResizeTest.php | 23 ++++++++++- 3 files changed, 62 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php index 4a55714a27ec5..4b7a623b15c19 100644 --- a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php +++ b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php @@ -68,11 +68,12 @@ public function __construct( * Build image params * * @param array $imageArguments + * @param int $scopeId * @return array * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function build(array $imageArguments): array + public function build(array $imageArguments, int $scopeId = null): array { $miscParams = [ 'image_type' => $imageArguments['type'] ?? null, @@ -81,7 +82,7 @@ public function build(array $imageArguments): array ]; $overwritten = $this->overwriteDefaultValues($imageArguments); - $watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type']) : []; + $watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type'], $scopeId) : []; return array_merge($miscParams, $overwritten, $watermark); } @@ -117,27 +118,32 @@ private function overwriteDefaultValues(array $imageArguments): array * Get watermark * * @param string $type + * @param int $scopeId * @return array */ - private function getWatermark(string $type): array + private function getWatermark(string $type, int $scopeId = null): array { $file = $this->scopeConfig->getValue( "design/watermark/{$type}_image", - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $scopeId ); if ($file) { $size = $this->scopeConfig->getValue( "design/watermark/{$type}_size", - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $scopeId ); $opacity = $this->scopeConfig->getValue( "design/watermark/{$type}_imageOpacity", - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $scopeId ); $position = $this->scopeConfig->getValue( "design/watermark/{$type}_position", - ScopeInterface::SCOPE_STORE + ScopeInterface::SCOPE_STORE, + $scopeId ); $width = !empty($size['width']) ? $size['width'] : null; $height = !empty($size['height']) ? $size['height'] : null; diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php index 90cdcb7159b0c..63353b2536a5a 100644 --- a/app/code/Magento/MediaStorage/Service/ImageResize.php +++ b/app/code/Magento/MediaStorage/Service/ImageResize.php @@ -7,10 +7,12 @@ namespace Magento\MediaStorage\Service; +use Generator; use Magento\Catalog\Helper\Image as ImageHelper; use Magento\Catalog\Model\Product\Image\ParamsBuilder; use Magento\Catalog\Model\View\Asset\ImageFactory as AssertImageFactory; use Magento\Framework\App\Area; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Filesystem; use Magento\Framework\Image; @@ -19,10 +21,12 @@ use Magento\Framework\App\State; use Magento\Framework\View\ConfigInterface as ViewConfig; use \Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage; +use Magento\Store\Model\StoreManagerInterface; use Magento\Theme\Model\Config\Customization as ThemeCustomizationConfig; use Magento\Theme\Model\ResourceModel\Theme\Collection; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\MediaStorage\Helper\File\Storage\Database; +use Magento\Theme\Model\Theme; /** * Image resize service. @@ -90,6 +94,10 @@ class ImageResize * @var Database */ private $fileStorageDatabase; + /** + * @var StoreManagerInterface + */ + private $storeManager; /** * @param State $appState @@ -103,6 +111,8 @@ class ImageResize * @param Collection $themeCollection * @param Filesystem $filesystem * @param Database $fileStorageDatabase + * @param StoreManagerInterface $storeManager + * @throws \Magento\Framework\Exception\FileSystemException * @internal param ProductImage $gallery * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -117,7 +127,8 @@ public function __construct( ThemeCustomizationConfig $themeCustomizationConfig, Collection $themeCollection, Filesystem $filesystem, - Database $fileStorageDatabase = null + Database $fileStorageDatabase = null, + StoreManagerInterface $storeManager = null ) { $this->appState = $appState; $this->imageConfig = $imageConfig; @@ -131,7 +142,8 @@ public function __construct( $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); $this->filesystem = $filesystem; $this->fileStorageDatabase = $fileStorageDatabase ?: - \Magento\Framework\App\ObjectManager::getInstance()->get(Database::class); + ObjectManager::getInstance()->get(Database::class); + $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** @@ -163,10 +175,10 @@ public function resizeFromImageName(string $originalImageName) * Create resized images of different sizes from themes. * * @param array|null $themes - * @return \Generator + * @return Generator * @throws NotFoundException */ - public function resizeFromThemes(array $themes = null): \Generator + public function resizeFromThemes(array $themes = null): Generator { $count = $this->productImage->getCountUsedProductImages(); if (!$count) { @@ -226,7 +238,8 @@ private function getThemesInUse(): array private function getViewImages(array $themes): array { $viewImages = []; - /** @var \Magento\Theme\Model\Theme $theme */ + $stores = $this->storeManager->getStores(true); + /** @var Theme $theme */ foreach ($themes as $theme) { $config = $this->viewConfig->getViewConfig( [ @@ -236,9 +249,12 @@ private function getViewImages(array $themes): array ); $images = $config->getMediaEntities('Magento_Catalog', ImageHelper::MEDIA_TYPE_CONFIG_NODE); foreach ($images as $imageId => $imageData) { - $uniqIndex = $this->getUniqueImageIndex($imageData); - $imageData['id'] = $imageId; - $viewImages[$uniqIndex] = $imageData; + foreach ($stores as $store) { + $data = $this->paramsBuilder->build($imageData, (int) $store->getId()); + $uniqIndex = $this->getUniqueImageIndex($data); + $data['id'] = $imageId; + $viewImages[$uniqIndex] = $data; + } } } return $viewImages; @@ -280,13 +296,13 @@ private function makeImage(string $originalImagePath, array $imageParams): Image /** * Resize image. * - * @param array $viewImage + * @param array $imageParams * @param string $originalImagePath * @param string $originalImageName */ - private function resize(array $viewImage, string $originalImagePath, string $originalImageName) + private function resize(array $imageParams, string $originalImagePath, string $originalImageName) { - $imageParams = $this->paramsBuilder->build($viewImage); + unset($imageParams['id']); $image = $this->makeImage($originalImagePath, $imageParams); $imageAsset = $this->assertImageFactory->create( [ diff --git a/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php index d612fb9e3b67b..f0e1efa7806e4 100644 --- a/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php +++ b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php @@ -17,6 +17,7 @@ use Magento\Framework\View\ConfigInterface as ViewConfig; use Magento\Framework\Config\View; use Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage; +use Magento\Store\Model\StoreManagerInterface; use Magento\Theme\Model\Config\Customization as ThemeCustomizationConfig; use Magento\Theme\Model\ResourceModel\Theme\Collection; use Magento\MediaStorage\Helper\File\Storage\Database; @@ -119,7 +120,15 @@ class ImageResizeTest extends \PHPUnit\Framework\TestCase * @var string */ private $testfilepath; + /** + * @var \PHPUnit\Framework\MockObject\MockObject|StoreManagerInterface + */ + private $storeManager; + /** + * @inheritDoc + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ protected function setUp() { $this->testfilename = "image.jpg"; @@ -139,6 +148,7 @@ protected function setUp() $this->themeCollectionMock = $this->createMock(Collection::class); $this->filesystemMock = $this->createMock(Filesystem::class); $this->databaseMock = $this->createMock(Database::class); + $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class); $this->mediaDirectoryMock = $this->getMockBuilder(Filesystem::class) ->disableOriginalConstructor() @@ -203,6 +213,16 @@ protected function setUp() ->method('getViewConfig') ->willReturn($this->viewMock); + $store = $this->getMockForAbstractClass(\Magento\Store\Api\Data\StoreInterface::class); + $store + ->expects($this->any()) + ->method('getId') + ->willReturn(1); + $this->storeManager + ->expects($this->any()) + ->method('getStores') + ->willReturn([$store]); + $this->service = new \Magento\MediaStorage\Service\ImageResize( $this->appStateMock, $this->imageConfigMock, @@ -214,7 +234,8 @@ protected function setUp() $this->themeCustomizationConfigMock, $this->themeCollectionMock, $this->filesystemMock, - $this->databaseMock + $this->databaseMock, + $this->storeManager ); } From 6e16a8f97f0021d21f199c6ef9411ee1a05ba181 Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Wed, 21 Aug 2019 18:05:44 +0300 Subject: [PATCH 311/841] magento/magento2#24186: CLI: cron:install. Break tasks to default and non-optional. --- .../Console/Command/CronInstallCommand.php | 45 +++++++++++++++++-- app/code/Magento/Cron/etc/di.xml | 3 ++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php index a3087b4c3d730..131d0a7ca7e3e 100644 --- a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php +++ b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Cron\Console\Command; use Magento\Framework\Crontab\CrontabManagerInterface; @@ -19,6 +21,9 @@ */ class CronInstallCommand extends Command { + const COMMAND_OPTION_FORCE = 'force'; + const COMMAND_OPTION_NON_OPTIONAL = 'non-optional'; + /** * @var CrontabManagerInterface */ @@ -44,19 +49,27 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function configure() { $this->setName('cron:install') ->setDescription('Generates and installs crontab for current user') - ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force install tasks'); + ->addOption(self::COMMAND_OPTION_FORCE, 'f', InputOption::VALUE_NONE, 'Force install tasks') + // @codingStandardsIgnoreStart + ->addOption(self::COMMAND_OPTION_NON_OPTIONAL, 'd', InputOption::VALUE_NONE, 'Install only the non-optional (default) tasks'); + // @codingStandardsIgnoreEnd parent::configure(); } /** - * {@inheritdoc} + * Executes "cron:install" command. + * + * @param InputInterface $input + * @param OutputInterface $output + * @return int|null + * @throws LocalizedException */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -65,8 +78,13 @@ protected function execute(InputInterface $input, OutputInterface $output) return Cli::RETURN_FAILURE; } + $tasks = $this->tasksProvider->getTasks(); + if ($input->getOption(self::COMMAND_OPTION_NON_OPTIONAL)) { + $tasks = $this->extractNonOptionalTasks($tasks); + } + try { - $this->crontabManager->saveTasks($this->tasksProvider->getTasks()); + $this->crontabManager->saveTasks($tasks); } catch (LocalizedException $e) { $output->writeln('<error>' . $e->getMessage() . '</error>'); return Cli::RETURN_FAILURE; @@ -76,4 +94,23 @@ protected function execute(InputInterface $input, OutputInterface $output) return Cli::RETURN_SUCCESS; } + + /** + * Returns an array of non-optional tasks + * + * @param array $tasks + * @return array + */ + private function extractNonOptionalTasks(array $tasks = []): array + { + $defaultTasks = []; + + foreach ($tasks as $taskCode => $taskParams) { + if (!$taskParams['optional']) { + $defaultTasks[$taskCode] = $taskParams; + } + } + + return $defaultTasks; + } } diff --git a/app/code/Magento/Cron/etc/di.xml b/app/code/Magento/Cron/etc/di.xml index 3e3bdc2053576..eadfa15d49414 100644 --- a/app/code/Magento/Cron/etc/di.xml +++ b/app/code/Magento/Cron/etc/di.xml @@ -66,12 +66,15 @@ <argument name="tasks" xsi:type="array"> <item name="cronMagento" xsi:type="array"> <item name="command" xsi:type="string">{magentoRoot}bin/magento cron:run | grep -v "Ran jobs by schedule" >> {magentoLog}magento.cron.log</item> + <item name="optional" xsi:type="boolean">false</item> </item> <item name="cronUpdate" xsi:type="array"> <item name="command" xsi:type="string">{magentoRoot}update/cron.php >> {magentoLog}update.cron.log</item> + <item name="optional" xsi:type="boolean">true</item> </item> <item name="cronSetup" xsi:type="array"> <item name="command" xsi:type="string">{magentoRoot}bin/magento setup:cron:run >> {magentoLog}setup.cron.log</item> + <item name="optional" xsi:type="boolean">true</item> </item> </argument> </arguments> From 629ccde3e6cea99b79e53b9acd8459d758ce27be Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Wed, 21 Aug 2019 11:32:38 -0500 Subject: [PATCH 312/841] MC-19090: Category Image from Gallery is not saved --- app/code/Magento/Catalog/Model/Category/FileInfo.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php index 66a251f7ed448..76b6a2e75d0ea 100644 --- a/app/code/Magento/Catalog/Model/Category/FileInfo.php +++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php @@ -209,7 +209,7 @@ private function removeStorePath(string $path): string { $result = $path; try { - $storeUrl = $this->storeManager->getStore()->getUrl(); + $storeUrl = $this->storeManager->getStore()->getBaseUrl(); } catch (NoSuchEntityException $e) { return $result; } @@ -217,8 +217,9 @@ private function removeStorePath(string $path): string $path = parse_url($path, PHP_URL_PATH); // phpcs:ignore Magento2.Functions.DiscouragedFunction $storePath = parse_url($storeUrl, PHP_URL_PATH); - $result = ltrim($path, $storePath); + $storePath = rtrim($storePath, '/'); + $result = preg_replace('/^' . preg_quote($storePath, '/') . '/', '', $path); return $result; } From 536d9e66433b85eac9753474a8f184510469b6d3 Mon Sep 17 00:00:00 2001 From: Olga Kopylova <kopylova@adobe.com> Date: Wed, 21 Aug 2019 13:53:06 -0500 Subject: [PATCH 313/841] Added CODEOWNERS file - added architects based on components assignments --- .github/CODEOWNERS | 206 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000000..84b29393699c4 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,206 @@ +/app/code/Magento/AdminNotification/ @paliarush +/app/code/Magento/Backend/ @paliarush +/app/code/Magento/User/ @paliarush +/lib/internal/Magento/Framework/App/ @buskamuza +/lib/internal/Magento/Framework/Controller/ @buskamuza +/lib/internal/Magento/Framework/Flag/ @buskamuza +/lib/internal/Magento/Framework/HTTP/ @buskamuza +/lib/internal/Magento/Framework/Logger/ @buskamuza +/lib/internal/Magento/Framework/Message/ @buskamuza +/lib/internal/Magento/Framework/Notification/ @buskamuza +/lib/internal/Magento/Framework/Session/ @buskamuza +/lib/internal/Magento/Framework/Url/ @buskamuza +/app/code/Magento/Cms/ @Melnykov +/app/code/Magento/CmsUrlRewrite/ @Melnykov +/app/code/Magento/Contact/ @Melnykov +/app/code/Magento/Email/ @Melnykov +/app/code/Magento/Variable/ @Melnykov +/app/code/Magento/Widget/ @Melnykov +/lib/internal/Magento/Framework/Cache/ @kokoc +/app/code/Magento/CacheInvalidate/ @kokoc +/app/code/Magento/CatalogInventory/ @tariqjawed83 @maghamed +/app/code/Magento/Bundle/ @akaplya +/app/code/Magento/BundleImportExport/ @akaplya +/app/code/Magento/Catalog/ @akaplya +/app/code/Magento/CatalogAnalytics/ @akaplya +/app/code/Magento/CatalogImportExport/ @akaplya +/app/code/Magento/CatalogSearch/ @kokoc +/app/code/Magento/CatalogUrlRewrite/ @akaplya +/app/code/Magento/ConfigurableImportExport/ @akaplya +/app/code/Magento/ConfigurableProduct/ @akaplya +/app/code/Magento/Downloadable/ @akaplya +/app/code/Magento/DownloadableImportExport/ @akaplya +/app/code/Magento/GroupedImportExport/ @akaplya +/app/code/Magento/GroupedProduct/ @akaplya +/app/code/Magento/LayeredNavigation/ @kokoc +/app/code/Magento/ProductVideo/ @akaplya +/app/code/Magento/Review/ @akaplya +/app/code/Magento/Swatches/ @akaplya +/app/code/Magento/SwatchesLayeredNavigation/ @kokoc +/app/code/Magento/Checkout/ @paliarush +/app/code/Magento/CheckoutAgreements/ @paliarush +/app/code/Magento/GiftMessage/ @paliarush +/app/code/Magento/InstantPurchase/ @paliarush +/app/code/Magento/Multishipping/ @joni-jones +/app/code/Magento/Quote/ @paliarush +/app/code/Magento/QuoteAnalytics/ @paliarush +/lib/internal/Magento/Framework/Code/ @joni-jones +/lib/internal/Magento/Framework/Reflection/ @joni-jones +/lib/internal/Magento/Framework/Component/ @buskamuza +/app/code/Magento/Version/ @buskamuza +/lib/internal/Magento/Framework/Config/ @paliarush +/app/code/Magento/Config/ @paliarush +/lib/internal/Magento/Framework/Console/ @joni-jones +/lib/internal/Magento/Framework/Process/ @joni-jones +/lib/internal/Magento/Framework/Shell/ @joni-jones +/app/code/Magento/Cookie/ @kokoc +/lib/internal/Magento/Framework/Crontab/ @tariqjawed83 @buskamuza +/app/code/Magento/Cron/ @tariqjawed83 @buskamuza +/app/code/Magento/Customer/ @paliarush +/app/code/Magento/CustomerAnalytics/ @paliarush +/app/code/Magento/CustomerImportExport/ @paliarush +/app/code/Magento/Persistent/ @paliarush +/app/code/Magento/Wishlist/ @paliarush +/lib/internal/Magento/Framework/DB/ @akaplya +/lib/internal/Magento/Framework/EntityManager/ @akaplya +/lib/internal/Magento/Framework/Indexer/ @akaplya +/lib/internal/Magento/Framework/Model/ @akaplya +/lib/internal/Magento/Framework/Mview/ @akaplya +/app/code/Magento/Eav/ @akaplya +/app/code/Magento/Indexer/ @akaplya +/lib/internal/Magento/Framework/Archive/ @joni-jones +/lib/internal/Magento/Framework/Convert/ @joni-jones +/lib/internal/Magento/Framework/Data/ @joni-jones +/lib/internal/Magento/Framework/DomDocument/ @joni-jones +/lib/internal/Magento/Framework/Json/ @joni-jones +/lib/internal/Magento/Framework/Math/ @joni-jones +/lib/internal/Magento/Framework/Parse/ @joni-jones +/lib/internal/Magento/Framework/Serialize/ @joni-jones +/lib/internal/Magento/Framework/Simplexml/ @joni-jones +/lib/internal/Magento/Framework/Stdlib/ @joni-jones +/lib/internal/Magento/Framework/Unserialize/ @joni-jones +/lib/internal/Magento/Framework/Xml/ @joni-jones +/lib/internal/Magento/Framework/XsltProcessor/ @joni-jones +/app/code/Magento/Deploy/ @kandy @buskamuza +/lib/internal/Magento/Framework/Profiler/ @kandy +/app/code/Magento/Developer/ @buskamuza +/app/code/Magento/Directory/ @buskamuza +/lib/internal/Magento/Framework/Exception/ @paliarush +/lib/internal/Magento/Framework/File/ @buskamuza +/lib/internal/Magento/Framework/Filesystem/ @buskamuza +/lib/internal/Magento/Framework/System/ @buskamuza +/lib/internal/Magento/Framework/Css/ @DrewML +/lib/internal/Magento/Framework/Option/ @DrewML +/lib/internal/Magento/Framework/RequireJs/ @DrewML +/lib/internal/Magento/Framework/View/ @Melnykov +/dev/tests/js/ @DrewML +/app/code/Magento/RequireJs/ @DrewML +/app/code/Magento/Theme/ @Melnykov +/app/code/Magento/Ui/ @Melnykov +/lib/internal/Magento/Framework/Intl/ @Melnykov +/lib/internal/Magento/Framework/Locale/ @Melnykov +/lib/internal/Magento/Framework/Phrase/ @Melnykov +/lib/internal/Magento/Framework/Translate/ @Melnykov +/app/code/Magento/Translation/ @Melnykov +/app/code/Magento/ImportExport/ @akaplya +/app/code/Magento/GoogleAdwords/ @buskamuza @Melnykov +/app/code/Magento/Newsletter/ @buskamuza @Melnykov +/app/code/Magento/ProductAlert/ @buskamuza @Melnykov +/app/code/Magento/Rss/ @buskamuza @Melnykov +/app/code/Magento/SendFriend/ @buskamuza @Melnykov +/app/code/Magento/Marketplace/ @buskamuza +/app/code/Magento/MediaStorage/ @buskamuza +/lib/internal/Magento/Framework/Amqp/ @tariqjawed83 @paliarush +/lib/internal/Magento/Framework/Bulk/ @tariqjawed83 @paliarush +/lib/internal/Magento/Framework/Communication/ @tariqjawed83 @paliarush +/app/code/Magento/Amqp/ @tariqjawed83 @paliarush +/app/code/Magento/AsynchronousOperations/ @tariqjawed83 @paliarush +/app/code/Magento/MessageQueue/ @tariqjawed83 @paliarush +/app/code/Magento/MysqlMq/ @tariqjawed83 @paliarush +/app/code/Magento/Sales/ @joni-jones +/app/code/Magento/SalesInventory/ @joni-jones +/app/code/Magento/SalesSequence/ @joni-jones +/lib/internal/Magento/Framework/Event/ @buskamuza @kandy +/lib/internal/Magento/Framework/Interception/ @buskamuza @kandy +/lib/internal/Magento/Framework/ObjectManager/ @buskamuza @kandy +/app/code/Magento/PageCache/ @Andrey @kokoc @paliarush +/app/code/Magento/Authorizenet/ @joni-jones +/app/code/Magento/Braintree/ @joni-jones +/app/code/Magento/OfflinePayments/ @joni-jones +/app/code/Magento/Payment/ @joni-jones +/app/code/Magento/Paypal/ @joni-jones +/app/code/Magento/Signifyd/ @joni-jones +/app/code/Magento/Vault/ @joni-jones +/lib/internal/Magento/Framework/Pricing/ @akaplya +/app/code/Magento/AdvancedPricingImportExport/ @akaplya +/app/code/Magento/CurrencySymbol/ @akaplya +/app/code/Magento/Msrp/ @akaplya +/app/code/Magento/Tax/ @akaplya +/app/code/Magento/TaxImportExport/ @akaplya +/app/code/Magento/Weee/ @akaplya +/app/code/Magento/CatalogRule/ @kokoc +/app/code/Magento/CatalogRuleConfigurable/ @kokoc +/app/code/Magento/CatalogWidget/ @kokoc +/app/code/Magento/Rule/ @kokoc +/app/code/Magento/SalesRule/ @akaplya +/app/code/Magento/ReleaseNotification/ @paliarush +/app/code/Magento/Analytics/ @tariqjawed83 @buskamuza +/app/code/Magento/GoogleAnalytics/ @tariqjawed83 @buskamuza +/app/code/Magento/NewRelicReporting/ @tariqjawed83 @buskamuza +/app/code/Magento/Reports/ @tariqjawed83 @buskamuza +/app/code/Magento/ReviewAnalytics/ @tariqjawed83 @buskamuza +/app/code/Magento/SalesAnalytics/ @tariqjawed83 @buskamuza +/app/code/Magento/WishlistAnalytics/ @tariqjawed83 @buskamuza +/app/code/Magento/GoogleOptimizer/ @paliarush +/app/code/Magento/Robots/ @paliarush +/app/code/Magento/Sitemap/ @paliarush +/lib/internal/Magento/Framework/Search/ @kokoc +/app/code/Magento/AdvancedSearch/ @kokoc +/app/code/Magento/Elasticsearch/ @kokoc +/app/code/Magento/Search/ @kokoc +/lib/internal/Magento/Framework/Acl/ @kokoc +/lib/internal/Magento/Framework/Authorization/ @kokoc +/lib/internal/Magento/Framework/Encryption/ @kokoc +/app/code/Magento/Authorization/ @kokoc +/app/code/Magento/Captcha/ @kokoc +/app/code/Magento/EncryptionKey/ @kokoc +/app/code/Magento/Security/ @kokoc +/lib/internal/Magento/Framework/Autoload/ @buskamuza +/lib/internal/Magento/Framework/Backup/ @buskamuza +/lib/internal/Magento/Framework/Composer/ @buskamuza +/lib/internal/Magento/Framework/Setup/ @buskamuza +/app/code/Magento/Backup/ @buskamuza +/setup/ @buskamuza +/app/code/Magento/Dhl/ @joni-jones +/app/code/Magento/Fedex/ @joni-jones +/app/code/Magento/OfflineShipping/ @joni-jones +/app/code/Magento/Shipping/ @joni-jones +/app/code/Magento/Ups/ @joni-jones +/app/code/Magento/Usps/ @joni-jones +/app/code/Magento/Store/ @akaplya +/lib/internal/Magento/Framework/TestFramework/ @paliarush +/dev/tests/integration/framework/ @buskamuza +/dev/tests/setup-integration/framework/ @paliarush +/dev/tests/static/framework/ @paliarush +/dev/tests/unit/ @paliarush +/dev/tests/api-functional/ @paliarush +/app/code/Magento/UrlRewrite/ @kokoc +/lib/internal/Magento/Framework/Image/ @buskamuza +/lib/internal/Magento/Framework/Mail/ @Melnykov +/lib/internal/Magento/Framework/Filter/ @Melnykov +/lib/internal/Magento/Framework/Validation/ @Melnykov +/lib/internal/Magento/Framework/Validator/ @Melnykov +/lib/internal/Magento/Framework/Api/ @paliarush +/lib/internal/Magento/Framework/GraphQL/ @paliarush +/lib/internal/Magento/Framework/Oauth/ @paliarush +/lib/internal/Magento/Framework/Webapi/ @paliarush +/app/code/Magento/GraphQL/ @paliarush +/app/code/Magento/Integration/ @paliarush +/app/code/Magento/Swagger/ @paliarush +/app/code/Magento/Webapi/ @paliarush +/app/code/Magento/WebapiSecurity/ @paliarush + +composer.json @buskamuza +*.js @DrewML +.htaccess* @akaplya +nginx.conf* @akaplya From d2ecb286acf51d9a1c6cb897a3ce86c9625e7535 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Wed, 21 Aug 2019 14:47:18 -0500 Subject: [PATCH 314/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php index e8f07d3135f49..e38eccc73597e 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php @@ -118,9 +118,11 @@ public function checkLogsExists($expectedLinesCount) /** * Workaround for https://bugs.php.net/bug.php?id=72286 + * phpcs:disable Magento2.Functions.StaticFunction */ - public function tearDownAfterClass() + public static function tearDownAfterClass() { + // phpcs:enable Magento2.Functions.StaticFunction if (version_compare(phpversion(), '7') == -1) { $closeConnection = new \ReflectionMethod(\Magento\Amqp\Model\Config::class, 'closeConnection'); $closeConnection->setAccessible(true); From 6c68b927972e4ce81b7280c991475dc57ae4bbc1 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Wed, 21 Aug 2019 14:47:59 -0500 Subject: [PATCH 315/841] MC-19099: Add tests for the Reading deprecated annotation from the schema - Added tests for the reading deprecated annotation --- .../GraphQlDeprecatedAnnotationReaderTest.php | 228 +++++ .../Framework/GraphQl/_files/schemaE.graphqls | 25 + ...ema_response_sdl_deprecated_annotation.php | 856 ++++++++++++++++++ 3 files changed, 1109 insertions(+) create mode 100644 dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls create mode 100644 dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php new file mode 100644 index 0000000000000..44fb6dbe762f4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php @@ -0,0 +1,228 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config; + +use Magento\Framework\App\Cache; +use Magento\Framework\App\Request\Http; +use Magento\Framework\GraphQl\Config; +use Magento\Framework\GraphQl\Schema\SchemaGenerator; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\GraphQl\Controller\GraphQl; + +/** + * Tests the entire process of generating a schema from a given SDL and processing a request/query + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @magentoAppArea graphql + */ +class GraphQlDeprecatedAnnotationReaderTest extends \PHPUnit\Framework\TestCase +{ + /** @var Config */ + private $configModel; + + /** @var GraphQl */ + private $graphQlController; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** @var SerializerInterface */ + private $jsonSerializer; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var Cache $cache */ + $cache = $this->objectManager->get(Cache::class); + $cache->clean(); + $fileResolverMock = $this->getMockBuilder( + \Magento\Framework\Config\FileResolverInterface::class + )->disableOriginalConstructor()->getMock(); + $fileList = [ + file_get_contents(__DIR__ . '/../_files/schemaE.graphqls') + ]; + $fileResolverMock->expects($this->any())->method('get')->will($this->returnValue($fileList)); + $graphQlReader = $this->objectManager->create( + \Magento\Framework\GraphQlSchemaStitching\GraphQlReader::class, + ['fileResolver' => $fileResolverMock] + ); + $reader = $this->objectManager->create( + \Magento\Framework\GraphQlSchemaStitching\Reader::class, + ['readers' => ['graphql_reader' => $graphQlReader]] + ); + $data = $this->objectManager->create( + \Magento\Framework\GraphQl\Config\Data ::class, + ['reader' => $reader] + ); + $this->configModel = $this->objectManager->create( + \Magento\Framework\GraphQl\Config::class, + ['data' => $data] + ); + $outputMapper = $this->objectManager->create( + \Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper::class, + ['config' => $this->configModel] + ); + $schemaGenerator = $this->objectManager->create( + SchemaGenerator::class, + ['outputMapper' => $outputMapper] + ); + $this->graphQlController = $this->objectManager->create( + GraphQl::class, + ['schemaGenerator' => $schemaGenerator] + ); + $this->jsonSerializer = $this->objectManager->get(SerializerInterface::class); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testDispatchIntrospectionWithCustomSDL() + { + $query + = <<<QUERY + query IntrospectionQuery { + __schema { + queryType { name } + types { + ...FullType + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} + + +QUERY; + $postData = [ + 'query' => $query, + 'variables' => null, + 'operationName' => 'IntrospectionQuery' + ]; + /** @var Http $request */ + $request = $this->objectManager->get(Http::class); + $request->setPathInfo('/graphql'); + $request->setMethod('POST'); + $request->setContent(json_encode($postData)); + $headers = $this->objectManager->create(\Zend\Http\Headers::class) + ->addHeaders(['Content-Type' => 'application/json']); + $request->setHeaders($headers); + + $response = $this->graphQlController->dispatch($request); + $this->jsonSerializer->unserialize($response->getContent()); + $expectedOutput = require __DIR__ . '/../_files/schema_response_sdl_deprecated_annotation.php'; + + //Checks to make sure that the given description exists in the expectedOutput array + + $this->assertTrue( + array_key_exists( + array_search( + 'Comment for SortEnum', + array_column($expectedOutput, 'description') + ), + $expectedOutput + ) + ); + + //Checks to make sure that the given deprecatedReason exists in the expectedOutput array for enumValues, fields. + $fieldsArray = $expectedOutput[0]['fields']; + $enumValuesArray = $expectedOutput[1]['enumValues']; + + foreach($fieldsArray as $field){ + if ( $field['isDeprecated'] === true){ + $typeDeprecatedReason = $field['deprecationReason']; + } + } + $this->assertNotEmpty($typeDeprecatedReason); + $this->assertEquals('Deprecated url_path test', $typeDeprecatedReason); + + foreach($enumValuesArray as $enumValue){ + if ( $enumValue['isDeprecated'] === true){ + $enumValueDeprecatedReason = $enumValue['deprecationReason']; + } + } + + $this->assertNotEmpty($enumValueDeprecatedReason); + $this->assertEquals('Deprecated SortEnum Value test',$enumValueDeprecatedReason); + + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls new file mode 100644 index 0000000000000..0fd1a011f3584 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls @@ -0,0 +1,25 @@ +type Query { + placeholder: String @doc(description: "comment for placeholder.") +} + +type SimpleProduct { + url_key: String @doc(description: "comment for url_key for simple product that implements [ProductInterface]") + url_path: String @deprecated(reason:"Deprecated url_path test") +} + +enum SortEnum @doc(description: "Comment for SortEnum.") +{ + ASC @doc(description:"Ascending Order") + DESC @deprecated(reason:"Deprecated SortEnum Value test") +} + +type SearchResultPageInfo @doc(description:"Comment for SearchResultPageInfo") +{ + page_size: Int @doc(description:"Comment for page_size") + current_page: Int @doc(description:"Comment for current_page") +} + +interface ProductInterface { + url_key: String @doc(description: "comment for url_key inside ProductInterface type.") + url_path: String +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php new file mode 100644 index 0000000000000..ea4b290f1edc8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php @@ -0,0 +1,856 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + [ + 'kind'=> 'OBJECT', + 'name'=> 'SimpleProduct', + 'description'=> 'Comment for empty SimpleProduct type', + 'fields'=> [ + [ + 'name'=> 'options', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'INTERFACE', + 'name'=> 'CustomizableOptionInterface', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'url_key', + 'description'=> 'comment for url_key for simple product that implements [ProductInterface]', + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'url_path', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> true, + 'deprecationReason'=> 'Deprecated url_path test' + ], + [ + 'name'=> 'id', + 'description'=> 'comment for [ProductInterface].', + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'name', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'sku', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'special_price', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Float', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'special_from_date', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'attribute_set_id', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'tier_price', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Float', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'category_ids', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'updated_at', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'country_of_manufacture', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'type_id', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'website_ids', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'category_links', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'ProductCategoryLinks', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'product_links', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'INTERFACE', + 'name'=> 'ProductLinksInterface', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'media_gallery_entries', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'MediaGalleryEntry', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'tier_prices', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'ProductTierPrices', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'price', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'ProductPrices', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'manufacturer', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ] + ], + 'inputFields'=> null, + 'interfaces'=> [ + [ + 'kind'=> 'INTERFACE', + 'name'=> 'ProductInterface', + 'ofType'=> null + ], + [ + 'kind'=> 'INTERFACE', + 'name'=> 'PhysicalProductInterface', + 'ofType'=> null + ], + [ + 'kind'=> 'INTERFACE', + 'name'=> 'CustomizableProductInterface', + 'ofType'=> null + ] + ], + 'enumValues'=> null, + 'possibleTypes'=> null + ], + [ + 'kind' => 'ENUM', + 'name' => 'SortEnum', + 'description' => 'Comment for SortEnum', + 'fields' => null, + 'inputFields' => null, + 'interfaces' => null, + 'enumValues' => [ + [ + 'name' => 'ASC', + 'description' => 'Ascending Order', + 'isDeprecated' => false, + 'deprecationReason' => '' + ], + [ + 'name' => 'DESC', + 'description' => '', + 'isDeprecated' => true, + 'deprecationReason' => 'Deprecated SortEnum Value test' + ] + ], + 'possibleTypes' => null + ], + [ + 'kind' => 'OBJECT', + 'name' => 'Query', + 'description' => null, + 'fields' => [ + [ + 'name' => 'placeholder', + 'description' => 'comment for placeholder.', + 'args' => [ + + ], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null + ], + 'isDeprecated' => false, + 'deprecationReason' => null + ], + [ + 'name' => 'products', + 'description' => 'comment for products fields', + 'args' => [ + [ + 'name' => 'search', + 'description' => '', + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'String', + 'ofType' => null + ], + 'defaultValue' => null + ], + [ + 'name' => 'filter', + 'description' => '', + 'type' => [ + 'kind' => 'INPUT_OBJECT', + 'name' => 'ProductFilterInput', + 'ofType' => null + ], + 'defaultValue' => null + ], + [ + 'name' => 'pageSize', + 'description' => '', + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'Int', + 'ofType' => null + ], + 'defaultValue' => null + ], + [ + 'name' => 'currentPage', + 'description' => '', + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'Int', + 'ofType' => null + ], + 'defaultValue' => null + ], + [ + 'name' => 'sort', + 'description' => '', + 'type' => [ + 'kind' => 'INPUT_OBJECT', + 'name' => 'ProductSortInput', + 'ofType' => null + ], + 'defaultValue' => null + ] + ], + 'type' => [ + 'kind' => 'OBJECT', + 'name' => 'Products', + 'ofType' => null + ], + 'isDeprecated' => false, + 'deprecationReason' => null + ] + ], + 'inputFields' => null, + 'interfaces' => [ + + ], + 'enumValues' => null, + 'possibleTypes' => null + ], + [ + 'kind' => 'OBJECT', + 'name' => 'Products', + 'description' => 'Comment for Products', + 'fields' => [ + [ + 'name' => 'items', + 'description' => 'comment for items[Products].', + 'args' => [ + + ], + 'type' => [ + 'kind' => 'LIST', + 'name' => null, + 'ofType' => [ + 'kind' => 'INTERFACE', + 'name' => 'ProductInterface', + 'ofType' => null + ] + ], + 'isDeprecated' => false, + 'deprecationReason' => null + ], + [ + 'name' => 'page_info', + 'description' => 'comment for page_info.', + 'args' => [ + + ], + 'type' => [ + 'kind' => 'OBJECT', + 'name' => 'SearchResultPageInfo', + 'ofType' => null + ], + 'isDeprecated' => false, + 'deprecationReason' => null + ], + [ + 'name' => 'total_count', + 'description' => null, + 'args' => [ + + ], + 'type' => [ + 'kind' => 'SCALAR', + 'name' => 'Int', + 'ofType' => null + ], + 'isDeprecated' => false, + 'deprecationReason' => null + ] + ], + 'inputFields' => null, + 'interfaces' => [ + + ], + 'enumValues' => null, + 'possibleTypes' => null + ], + [ + 'kind'=> 'INTERFACE', + 'name'=> 'ProductInterface', + 'description'=> 'comment for ProductInterface', + 'fields'=> [ + [ + 'name'=> 'url_key', + 'description'=> 'comment for url_key inside ProductInterface type.', + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'url_path', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'id', + 'description'=> 'comment for [ProductInterface].', + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'name', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'sku', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'special_price', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Float', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'special_from_date', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'attribute_set_id', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'tier_price', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Float', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'category_ids', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'updated_at', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'country_of_manufacture', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'type_id', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'String', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'website_ids', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'category_links', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'ProductCategoryLinks', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'product_links', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'INTERFACE', + 'name'=> 'ProductLinksInterface', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'media_gallery_entries', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'MediaGalleryEntry', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'tier_prices', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'LIST', + 'name'=> null, + 'ofType'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'ProductTierPrices', + 'ofType'=> null + ] + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'price', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'OBJECT', + 'name'=> 'ProductPrices', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'manufacturer', + 'description'=> null, + 'args'=> [ + + ], + 'type'=> [ + 'kind'=> 'SCALAR', + 'name'=> 'Int', + 'ofType'=> null + ], + 'isDeprecated'=> false, + 'deprecationReason'=> null + ] + ], + 'inputFields'=> null, + 'interfaces'=> null, + 'enumValues'=> null, + 'possibleTypes'=> [ + [ + 'kind'=> 'OBJECT', + 'name'=> 'SimpleProduct', + 'ofType'=> null + ], + [ + 'kind'=> 'OBJECT', + 'name'=> 'VirtualProduct', + 'ofType'=> null + ] + ] + ], +]; From e58e5e4f00f15c6081cdb66074ad5405b021b5e6 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Wed, 21 Aug 2019 14:54:36 -0500 Subject: [PATCH 316/841] MC-19099: Add tests for the Reading deprecated annotation from the schema - Added changes in test for the reading deprecated annotation --- .../Config/GraphQlDeprecatedAnnotationReaderTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php index 44fb6dbe762f4..a70bd2cacdb35 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php @@ -209,20 +209,20 @@ enumValues(includeDeprecated: true) { foreach($fieldsArray as $field){ if ( $field['isDeprecated'] === true){ - $typeDeprecatedReason = $field['deprecationReason']; + $typeDeprecatedReason [] = $field['deprecationReason']; } } $this->assertNotEmpty($typeDeprecatedReason); - $this->assertEquals('Deprecated url_path test', $typeDeprecatedReason); + $this->assertContains('Deprecated url_path test', $typeDeprecatedReason); foreach($enumValuesArray as $enumValue){ if ( $enumValue['isDeprecated'] === true){ - $enumValueDeprecatedReason = $enumValue['deprecationReason']; + $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; } } $this->assertNotEmpty($enumValueDeprecatedReason); - $this->assertEquals('Deprecated SortEnum Value test',$enumValueDeprecatedReason); + $this->assertContains('Deprecated SortEnum Value test',$enumValueDeprecatedReason); } } From 6901a9c20322c09249bed30ed718c0a4294141af Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Wed, 21 Aug 2019 14:55:55 -0500 Subject: [PATCH 317/841] MC-18945: Reading deprecated annotation in schema - added static fixes --- .../Magento/Framework/GraphQl/Config/Element/EnumValue.php | 2 +- .../Framework/GraphQl/Config/Element/EnumValueFactory.php | 3 ++- .../Magento/Framework/GraphQl/Config/Element/FieldFactory.php | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php index 7cb48b40a64d5..b9ddc79e7fa4c 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php @@ -38,7 +38,7 @@ class EnumValue implements ConfigElementInterface * @param string $name * @param string $value * @param string $description - * @param string deprecationReason + * @param string $deprecationReason */ public function __construct(string $name, string $value, string $description = '', string $deprecationReason = '') { diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php index 804e4334f0fb2..45402d25f3d8a 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php @@ -38,7 +38,8 @@ public function __construct( * @return EnumValue */ public function create( - string $name, string $value, + string $name, + string $value, string $description = '', string $deprecationReason = '' ): EnumValue { diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php index dea50316aadeb..e4144b3038d33 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php @@ -37,6 +37,7 @@ public function __construct( * @param array $fieldData * @param array $arguments * @return Field + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function createFromConfigData( array $fieldData, From 6a4ecbe6ea560da3ff1bdba7a70a5a3fd1607d8e Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Wed, 21 Aug 2019 20:12:38 +0000 Subject: [PATCH 318/841] Added lines *Deleted unnecessary uses --- .../Customer/Test/Unit/Block/Form/RegisterTest.php | 3 +++ .../Observer/PredispatchNewsletterObserverTest.php | 11 +++++++++++ .../Magento/Customer/_files/customer_subscribe.php | 2 -- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php index e1adfdf4ed8f1..3022177ffb9e1 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php @@ -64,6 +64,7 @@ protected function setUp() $this->newsletterConfig = $this->createMock(\Magento\Newsletter\Model\Config::class); $context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class); $context->expects($this->any())->method('getScopeConfig')->will($this->returnValue($this->_scopeConfig)); + $this->_block = new \Magento\Customer\Block\Form\Register( $context, $this->directoryHelperMock, @@ -296,6 +297,7 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $isNewsletterActiv )->will( $this->returnValue($isNewsletterEnabled) ); + $this->newsletterConfig->expects( $this->any() )->method( @@ -303,6 +305,7 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $isNewsletterActiv )->will( $this->returnValue($isNewsletterActive) ); + $this->assertEquals($expectedValue, $this->_block->isNewsletterEnabled()); } diff --git a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php index f88537397ae66..d0cfa507eb42f 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php @@ -88,17 +88,21 @@ public function testNewsletterEnabled() : void ->disableOriginalConstructor() ->setMethods(['getResponse', 'getData', 'setRedirect']) ->getMockForAbstractClass(); + $this->newsletterConfig->expects($this->once()) ->method('isActive') ->with(ScopeInterface::SCOPE_STORE) ->willReturn(true); + $observerMock->expects($this->never()) ->method('getData') ->with('controller_action') ->willReturnSelf(); + $observerMock->expects($this->never()) ->method('getResponse') ->willReturnSelf(); + $this->assertNull($this->mockObject->execute($observerMock)); } @@ -111,27 +115,34 @@ public function testNewsletterDisabled() : void ->disableOriginalConstructor() ->setMethods(['getControllerAction', 'getResponse']) ->getMockForAbstractClass(); + $this->newsletterConfig->expects($this->once()) ->method('isActive') ->with(ScopeInterface::SCOPE_STORE) ->willReturn(false); + $expectedRedirectUrl = 'https://test.com/index'; $this->configMock->expects($this->once()) ->method('getValue') ->with('web/default/no_route', ScopeInterface::SCOPE_STORE) ->willReturn($expectedRedirectUrl); + $this->urlMock->expects($this->once()) ->method('getUrl') ->willReturn($expectedRedirectUrl); + $observerMock->expects($this->once()) ->method('getControllerAction') ->willReturnSelf(); + $observerMock->expects($this->once()) ->method('getResponse') ->willReturn($this->responseMock); + $this->responseMock->expects($this->once()) ->method('setRedirect') ->with($expectedRedirectUrl); + $this->assertNull($this->mockObject->execute($observerMock)); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php index 66ec54641d74e..c776eeab48ded 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php @@ -3,8 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -use Magento\Customer\Model\CustomerRegistry; - $resourceConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create(\Magento\Config\Model\ResourceModel\Config::class); From 79a52cdab65636e17f3c4188ff40a75a19b1a74f Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 21 Aug 2019 15:18:16 -0500 Subject: [PATCH 319/841] MC-19541: Fix the filtering by price --- .../Product/SearchCriteriaBuilder.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index 9e381307d8139..4872c627ded59 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -84,9 +84,11 @@ public function __construct( public function build(array $args, bool $includeAggregation): SearchCriteriaInterface { $searchCriteria = $this->builder->build('products', $args); + $this->updateRangeFilters($searchCriteria); $searchCriteria->setRequestName( $includeAggregation ? 'graphql_product_search_with_aggregation' : 'graphql_product_search' ); + if ($includeAggregation) { $this->preparePriceAggregation($searchCriteria); } @@ -175,4 +177,24 @@ private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria): v ->create(); $searchCriteria->setSortOrders([$sortOrder]); } + + /** + * Format range filters so replacement works + * + * Range filter fields in search request must replace value like '%field.from%' or '%field.to%' + * + * @param SearchCriteriaInterface $searchCriteria + */ + private function updateRangeFilters(SearchCriteriaInterface $searchCriteria): void + { + $filterGroups = $searchCriteria->getFilterGroups(); + foreach ($filterGroups as $filterGroup) { + $filters = $filterGroup->getFilters(); + foreach ($filters as $filter) { + if (in_array($filter->getConditionType(), ['from', 'to'])) { + $filter->setField($filter->getField() . '.' . $filter->getConditionType()); + } + } + } + } } From c5c87c34f336aece18b8adada6dec8885ab512d8 Mon Sep 17 00:00:00 2001 From: Dima Shevtsov <shevtsov@adobe.com> Date: Wed, 21 Aug 2019 15:27:50 -0500 Subject: [PATCH 320/841] Remove redundant file The file belongs to the devdocs project. It was added by accident in #24036 --- bin-magento.yml | 8832 ----------------------------------------------- 1 file changed, 8832 deletions(-) delete mode 100644 bin-magento.yml diff --git a/bin-magento.yml b/bin-magento.yml deleted file mode 100644 index 99ce03c1c0316..0000000000000 --- a/bin-magento.yml +++ /dev/null @@ -1,8832 +0,0 @@ ---- -application: - name: Magento CLI - version: 2.3.2 -commands: -- name: help - usage: - - help [--format FORMAT] [--raw] [--] [<command_name>] - description: Displays help for a command - help: |- - The <info>help</info> command displays help for a given command: - - <info>php bin/magento help list</info> - - You can also output the help in other formats by using the <comment>--format</comment> option: - - <info>php bin/magento help --format=xml list</info> - - To display the list of available commands, please use the <info>list</info> command. - definition: - arguments: - command_name: - name: command_name - is_required: false - is_array: false - description: The command name - default: help - options: - format: - name: "--format" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The output format (txt, xml, json, or md) - default: txt - raw: - name: "--raw" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: To output raw command help - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: list - usage: - - list [--raw] [--format FORMAT] [--] [<namespace>] - description: Lists commands - help: |- - The <info>list</info> command lists all commands: - - <info>php bin/magento list</info> - - You can also display the commands for a specific namespace: - - <info>php bin/magento list test</info> - - You can also output the information in other formats by using the <comment>--format</comment> option: - - <info>php bin/magento list --format=xml</info> - - It's also possible to get raw list of commands (useful for embedding command runner): - - <info>php bin/magento list --raw</info> - definition: - arguments: - namespace: - name: namespace - is_required: false - is_array: false - description: The namespace name - default: - options: - raw: - name: "--raw" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: To output raw command list - default: false - format: - name: "--format" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The output format (txt, xml, json, or md) - default: txt - hidden: false -- name: admin:user:create - usage: - - admin:user:create [--admin-user ADMIN-USER] [--admin-password ADMIN-PASSWORD] - [--admin-email ADMIN-EMAIL] [--admin-firstname ADMIN-FIRSTNAME] [--admin-lastname - ADMIN-LASTNAME] [--magento-init-params MAGENTO-INIT-PARAMS] - description: Creates an administrator - help: Creates an administrator - definition: - arguments: [] - options: - admin-user: - name: "--admin-user" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: "(Required) Admin user" - default: - admin-password: - name: "--admin-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: "(Required) Admin password" - default: - admin-email: - name: "--admin-email" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: "(Required) Admin email" - default: - admin-firstname: - name: "--admin-firstname" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: "(Required) Admin first name" - default: - admin-lastname: - name: "--admin-lastname" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: "(Required) Admin last name" - default: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: admin:user:unlock - usage: - - admin:user:unlock <username> - description: Unlock Admin Account - help: |- - This command unlocks an admin account by its username. - To unlock: - <comment>bin/magento admin:user:unlock username</comment> - definition: - arguments: - username: - name: username - is_required: true - is_array: false - description: The admin username to unlock - default: - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: app:config:dump - usage: - - app:config:dump [<config-types>...] - description: Create dump of application - help: Create dump of application - definition: - arguments: - config-types: - name: config-types - is_required: false - is_array: true - description: Space-separated list of config types or omit to dump all [scopes, - system, themes, i18n] - default: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: app:config:import - usage: - - app:config:import - description: Import data from shared configuration files to appropriate data storage - help: Import data from shared configuration files to appropriate data storage - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: app:config:status - usage: - - app:config:status - description: Checks if config propagation requires update - help: Checks if config propagation requires update - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cache:clean - usage: - - cache:clean [--bootstrap BOOTSTRAP] [--] [<types>...] - description: Cleans cache type(s) - help: Cleans cache type(s) - definition: - arguments: - types: - name: types - is_required: false - is_array: true - description: Space-separated list of cache types or omit to apply to all cache - types. - default: [] - options: - bootstrap: - name: "--bootstrap" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: add or override parameters of the bootstrap - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cache:disable - usage: - - cache:disable [--bootstrap BOOTSTRAP] [--] [<types>...] - description: Disables cache type(s) - help: Disables cache type(s) - definition: - arguments: - types: - name: types - is_required: false - is_array: true - description: Space-separated list of cache types or omit to apply to all cache - types. - default: [] - options: - bootstrap: - name: "--bootstrap" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: add or override parameters of the bootstrap - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cache:enable - usage: - - cache:enable [--bootstrap BOOTSTRAP] [--] [<types>...] - description: Enables cache type(s) - help: Enables cache type(s) - definition: - arguments: - types: - name: types - is_required: false - is_array: true - description: Space-separated list of cache types or omit to apply to all cache - types. - default: [] - options: - bootstrap: - name: "--bootstrap" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: add or override parameters of the bootstrap - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cache:flush - usage: - - cache:flush [--bootstrap BOOTSTRAP] [--] [<types>...] - description: Flushes cache storage used by cache type(s) - help: Flushes cache storage used by cache type(s) - definition: - arguments: - types: - name: types - is_required: false - is_array: true - description: Space-separated list of cache types or omit to apply to all cache - types. - default: [] - options: - bootstrap: - name: "--bootstrap" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: add or override parameters of the bootstrap - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cache:status - usage: - - cache:status [--bootstrap BOOTSTRAP] - description: Checks cache status - help: Checks cache status - definition: - arguments: [] - options: - bootstrap: - name: "--bootstrap" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: add or override parameters of the bootstrap - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: catalog:images:resize - usage: - - catalog:images:resize - description: Creates resized product images - help: Creates resized product images - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: catalog:product:attributes:cleanup - usage: - - catalog:product:attributes:cleanup - description: Removes unused product attributes. - help: Removes unused product attributes. - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: config:sensitive:set - usage: - - config:sensitive:set [-i|--interactive] [--scope [SCOPE]] [--scope-code [SCOPE-CODE]] - [--] [<path> [<value>]] - description: Set sensitive configuration values - help: Set sensitive configuration values - definition: - arguments: - path: - name: path - is_required: false - is_array: false - description: Configuration path for example group/section/field_name - default: - value: - name: value - is_required: false - is_array: false - description: Configuration value - default: - options: - interactive: - name: "--interactive" - shortcut: "-i" - accept_value: false - is_value_required: false - is_multiple: false - description: Enable interactive mode to set all sensitive variables - default: false - scope: - name: "--scope" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Scope for configuration, if not set use 'default' - default: default - scope-code: - name: "--scope-code" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Scope code for configuration, empty string by default - default: '' - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: config:set - usage: - - config:set [--scope SCOPE] [--scope-code SCOPE-CODE] [-le|--lock-env] [-lc|--lock-config] - [-l|--lock] [--] <path> <value> - description: Change system configuration - help: Change system configuration - definition: - arguments: - path: - name: path - is_required: true - is_array: false - description: Configuration path in format section/group/field_name - default: - value: - name: value - is_required: true - is_array: false - description: Configuration value - default: - options: - scope: - name: "--scope" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Configuration scope (default, website, or store) - default: default - scope-code: - name: "--scope-code" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Scope code (required only if scope is not 'default') - default: - lock-env: - name: "--lock-env" - shortcut: "-le" - accept_value: false - is_value_required: false - is_multiple: false - description: Lock value which prevents modification in the Admin (will be - saved in app/etc/env.php) - default: false - lock-config: - name: "--lock-config" - shortcut: "-lc" - accept_value: false - is_value_required: false - is_multiple: false - description: Lock and share value with other installations, prevents modification - in the Admin (will be saved in app/etc/config.php) - default: false - lock: - name: "--lock" - shortcut: "-l" - accept_value: false - is_value_required: false - is_multiple: false - description: Deprecated, use the --lock-env option instead. - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: config:show - usage: - - config:show [--scope [SCOPE]] [--scope-code [SCOPE-CODE]] [--] [<path>] - description: Shows configuration value for given path. If path is not specified, - all saved values will be shown - help: Shows configuration value for given path. If path is not specified, all saved - values will be shown - definition: - arguments: - path: - name: path - is_required: false - is_array: false - description: Configuration path, for example section_id/group_id/field_id - default: - options: - scope: - name: "--scope" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Scope for configuration, if not specified, then 'default' scope - will be used - default: default - scope-code: - name: "--scope-code" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Scope code (required only if scope is not `default`) - default: '' - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cron:install - usage: - - cron:install [-f|--force] - description: Generates and installs crontab for current user - help: Generates and installs crontab for current user - definition: - arguments: [] - options: - force: - name: "--force" - shortcut: "-f" - accept_value: false - is_value_required: false - is_multiple: false - description: Force install tasks - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cron:remove - usage: - - cron:remove - description: Removes tasks from crontab - help: Removes tasks from crontab - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: cron:run - usage: - - cron:run [--group GROUP] [--bootstrap BOOTSTRAP] - description: Runs jobs by schedule - help: Runs jobs by schedule - definition: - arguments: [] - options: - group: - name: "--group" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Run jobs only from specified group - default: - bootstrap: - name: "--bootstrap" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Add or override parameters of the bootstrap - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: customer:hash:upgrade - usage: - - customer:hash:upgrade - description: Upgrade customer's hash according to the latest algorithm - help: Upgrade customer's hash according to the latest algorithm - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: deploy:mode:set - usage: - - deploy:mode:set [-s|--skip-compilation] [--] <mode> - description: Set application mode. - help: Set application mode. - definition: - arguments: - mode: - name: mode - is_required: true - is_array: false - description: The application mode to set. Available options are "developer" - or "production" - default: - options: - skip-compilation: - name: "--skip-compilation" - shortcut: "-s" - accept_value: false - is_value_required: false - is_multiple: false - description: Skips the clearing and regeneration of static content (generated - code, preprocessed CSS, and assets in pub/static/) - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: deploy:mode:show - usage: - - deploy:mode:show - description: Displays current application mode. - help: Displays current application mode. - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:di:info - usage: - - dev:di:info <class> - description: Provides information on Dependency Injection configuration for the - Command. - help: Provides information on Dependency Injection configuration for the Command. - definition: - arguments: - class: - name: class - is_required: true - is_array: false - description: Class name - default: - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:profiler:disable - usage: - - dev:profiler:disable - description: Disable the profiler. - help: Disable the profiler. - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:profiler:enable - usage: - - dev:profiler:enable [<type>] - description: Enable the profiler. - help: Enable the profiler. - definition: - arguments: - type: - name: type - is_required: false - is_array: false - description: Profiler type - default: - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:query-log:disable - usage: - - dev:query-log:disable - description: Disable DB query logging - help: Disable DB query logging - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:query-log:enable - usage: - - dev:query-log:enable [--include-all-queries [INCLUDE-ALL-QUERIES]] [--query-time-threshold - [QUERY-TIME-THRESHOLD]] [--include-call-stack [INCLUDE-CALL-STACK]] - description: Enable DB query logging - help: Enable DB query logging - definition: - arguments: [] - options: - include-all-queries: - name: "--include-all-queries" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Log all queries. [true|false] - default: 'true' - query-time-threshold: - name: "--query-time-threshold" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Query time thresholds. - default: '0.001' - include-call-stack: - name: "--include-call-stack" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Include call stack. [true|false] - default: 'true' - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:source-theme:deploy - usage: - - dev:source-theme:deploy [--type TYPE] [--locale LOCALE] [--area AREA] [--theme - THEME] [--] [<file>...] - description: Collects and publishes source files for theme. - help: Collects and publishes source files for theme. - definition: - arguments: - file: - name: file - is_required: false - is_array: true - description: Files to pre-process (file should be specified without extension) - default: - - css/styles-m - - css/styles-l - options: - type: - name: "--type" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Type of source files: [less]' - default: less - locale: - name: "--locale" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Locale: [en_US]' - default: en_US - area: - name: "--area" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Area: [frontend|adminhtml]' - default: frontend - theme: - name: "--theme" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Theme: [Vendor/theme]' - default: Magento/luma - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:template-hints:disable - usage: - - dev:template-hints:disable - description: Disable frontend template hints. A cache flush might be required. - help: Disable frontend template hints. A cache flush might be required. - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:template-hints:enable - usage: - - dev:template-hints:enable - description: Enable frontend template hints. A cache flush might be required. - help: Enable frontend template hints. A cache flush might be required. - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:tests:run - usage: - - dev:tests:run [-c|--arguments ARGUMENTS] [--] [<type>] - description: Runs tests - help: Runs tests - definition: - arguments: - type: - name: type - is_required: false - is_array: false - description: 'Type of test to run. Available types: all, unit, integration, - integration-all, static, static-all, integrity, legacy, default' - default: default - options: - arguments: - name: "--arguments" - shortcut: "-c" - accept_value: true - is_value_required: true - is_multiple: false - description: 'Additional arguments for PHPUnit. Example: "-c''--filter=MyTest''" - (no spaces)' - default: '' - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:urn-catalog:generate - usage: - - dev:urn-catalog:generate [--ide IDE] [--] <path> - description: Generates the catalog of URNs to *.xsd mappings for the IDE to highlight - xml. - help: Generates the catalog of URNs to *.xsd mappings for the IDE to highlight xml. - definition: - arguments: - path: - name: path - is_required: true - is_array: false - description: Path to file to output the catalog. For PhpStorm use .idea/misc.xml - default: - options: - ide: - name: "--ide" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Format in which catalog will be generated. Supported: [phpstorm]' - default: phpstorm - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: dev:xml:convert - usage: - - dev:xml:convert [-o|--overwrite] [--] <xml-file> <processor> - description: Converts XML file using XSL style sheets - help: Converts XML file using XSL style sheets - definition: - arguments: - xml-file: - name: xml-file - is_required: true - is_array: false - description: Path to XML file that going to be transformed - default: - processor: - name: processor - is_required: true - is_array: false - description: Path to XSL style sheet that going to be applied to XML file - default: - options: - overwrite: - name: "--overwrite" - shortcut: "-o" - accept_value: false - is_value_required: false - is_multiple: false - description: Overwrite XML file - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: encryption:payment-data:update - usage: - - encryption:payment-data:update - description: Re-encrypts encrypted credit card data with latest encryption cipher. - help: Re-encrypts encrypted credit card data with latest encryption cipher. - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: i18n:collect-phrases - usage: - - i18n:collect-phrases [-o|--output OUTPUT] [-m|--magento] [--] [<directory>] - description: Discovers phrases in the codebase - help: Discovers phrases in the codebase - definition: - arguments: - directory: - name: directory - is_required: false - is_array: false - description: Directory path to parse. Not needed if --magento flag is set - default: - options: - output: - name: "--output" - shortcut: "-o" - accept_value: true - is_value_required: true - is_multiple: false - description: Path (including filename) to an output file. With no file specified, - defaults to stdout. - default: - magento: - name: "--magento" - shortcut: "-m" - accept_value: false - is_value_required: false - is_multiple: false - description: Use the --magento parameter to parse the current Magento codebase. - Omit the parameter if a directory is specified. - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: i18n:pack - usage: - - i18n:pack [-m|--mode MODE] [-d|--allow-duplicates] [--] <source> <locale> - description: Saves language package - help: Saves language package - definition: - arguments: - source: - name: source - is_required: true - is_array: false - description: Path to source dictionary file with translations - default: - locale: - name: locale - is_required: true - is_array: false - description: Target locale for dictionary, for example "de_DE" - default: - options: - mode: - name: "--mode" - shortcut: "-m" - accept_value: true - is_value_required: true - is_multiple: false - description: Save mode for dictionary - "replace" - replace language pack - by new one - "merge" - merge language packages, by default "replace" - default: replace - allow-duplicates: - name: "--allow-duplicates" - shortcut: "-d" - accept_value: false - is_value_required: false - is_multiple: false - description: Use the --allow-duplicates parameter to allow saving duplicates - of translate. Otherwise omit the parameter. - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: i18n:uninstall - usage: - - i18n:uninstall [-b|--backup-code] [--] <package>... - description: Uninstalls language packages - help: Uninstalls language packages - definition: - arguments: - package: - name: package - is_required: true - is_array: true - description: Language package name - default: [] - options: - backup-code: - name: "--backup-code" - shortcut: "-b" - accept_value: false - is_value_required: false - is_multiple: false - description: Take code and configuration files backup (excluding temporary - files) - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:info - usage: - - indexer:info - description: Shows allowed Indexers - help: Shows allowed Indexers - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:reindex - usage: - - indexer:reindex [<index>...] - description: Reindexes Data - help: Reindexes Data - definition: - arguments: - index: - name: index - is_required: false - is_array: true - description: Space-separated list of index types or omit to apply to all indexes. - default: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:reset - usage: - - indexer:reset [<index>...] - description: Resets indexer status to invalid - help: Resets indexer status to invalid - definition: - arguments: - index: - name: index - is_required: false - is_array: true - description: Space-separated list of index types or omit to apply to all indexes. - default: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:set-dimensions-mode - usage: - - indexer:set-dimensions-mode [<indexer> [<mode>]] - description: Set Indexer Dimensions Mode - help: Set Indexer Dimensions Mode - definition: - arguments: - indexer: - name: indexer - is_required: false - is_array: false - description: Indexer name [catalog_product_price] - default: - mode: - name: mode - is_required: false - is_array: false - description: 'Indexer dimension modes catalog_product_price none,website,customer_group,website_and_customer_group ' - default: - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:set-mode - usage: - - indexer:set-mode [<mode> [<index>...]] - description: Sets index mode type - help: Sets index mode type - definition: - arguments: - mode: - name: mode - is_required: false - is_array: false - description: Indexer mode type [realtime|schedule] - default: - index: - name: index - is_required: false - is_array: true - description: Space-separated list of index types or omit to apply to all indexes. - default: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:show-dimensions-mode - usage: - - indexer:show-dimensions-mode [<indexer>...] - description: Shows Indexer Dimension Mode - help: Shows Indexer Dimension Mode - definition: - arguments: - indexer: - name: indexer - is_required: false - is_array: true - description: Space-separated list of index types or omit to apply to all indexes - (catalog_product_price) - default: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:show-mode - usage: - - indexer:show-mode [<index>...] - description: Shows Index Mode - help: Shows Index Mode - definition: - arguments: - index: - name: index - is_required: false - is_array: true - description: Space-separated list of index types or omit to apply to all indexes. - default: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: indexer:status - usage: - - indexer:status [<index>...] - description: Shows status of Indexer - help: Shows status of Indexer - definition: - arguments: - index: - name: index - is_required: false - is_array: true - description: Space-separated list of index types or omit to apply to all indexes. - default: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:adminuri - usage: - - info:adminuri - description: Displays the Magento Admin URI - help: Displays the Magento Admin URI - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:backups:list - usage: - - info:backups:list - description: Prints list of available backup files - help: Prints list of available backup files - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:currency:list - usage: - - info:currency:list - description: Displays the list of available currencies - help: Displays the list of available currencies - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:dependencies:show-framework - usage: - - info:dependencies:show-framework [-o|--output OUTPUT] - description: Shows number of dependencies on Magento framework - help: Shows number of dependencies on Magento framework - definition: - arguments: [] - options: - output: - name: "--output" - shortcut: "-o" - accept_value: true - is_value_required: true - is_multiple: false - description: Report filename - default: framework-dependencies.csv - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:dependencies:show-modules - usage: - - info:dependencies:show-modules [-o|--output OUTPUT] - description: Shows number of dependencies between modules - help: Shows number of dependencies between modules - definition: - arguments: [] - options: - output: - name: "--output" - shortcut: "-o" - accept_value: true - is_value_required: true - is_multiple: false - description: Report filename - default: modules-dependencies.csv - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:dependencies:show-modules-circular - usage: - - info:dependencies:show-modules-circular [-o|--output OUTPUT] - description: Shows number of circular dependencies between modules - help: Shows number of circular dependencies between modules - definition: - arguments: [] - options: - output: - name: "--output" - shortcut: "-o" - accept_value: true - is_value_required: true - is_multiple: false - description: Report filename - default: modules-circular-dependencies.csv - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:language:list - usage: - - info:language:list - description: Displays the list of available language locales - help: Displays the list of available language locales - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: info:timezone:list - usage: - - info:timezone:list - description: Displays the list of available timezones - help: Displays the list of available timezones - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: maintenance:allow-ips - usage: - - maintenance:allow-ips [--none] [--add] [--magento-init-params MAGENTO-INIT-PARAMS] - [--] [<ip>...] - description: Sets maintenance mode exempt IPs - help: Sets maintenance mode exempt IPs - definition: - arguments: - ip: - name: ip - is_required: false - is_array: true - description: Allowed IP addresses - default: [] - options: - none: - name: "--none" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Clear allowed IP addresses - default: false - add: - name: "--add" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Add the IP address to existing list - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: maintenance:disable - usage: - - maintenance:disable [--ip IP] [--magento-init-params MAGENTO-INIT-PARAMS] - description: Disables maintenance mode - help: Disables maintenance mode - definition: - arguments: [] - options: - ip: - name: "--ip" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: true - description: Allowed IP addresses (use 'none' to clear allowed IP list) - default: [] - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: maintenance:enable - usage: - - maintenance:enable [--ip IP] [--magento-init-params MAGENTO-INIT-PARAMS] - description: Enables maintenance mode - help: Enables maintenance mode - definition: - arguments: [] - options: - ip: - name: "--ip" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: true - description: Allowed IP addresses (use 'none' to clear allowed IP list) - default: [] - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: maintenance:status - usage: - - maintenance:status [--magento-init-params MAGENTO-INIT-PARAMS] - description: Displays maintenance mode status - help: Displays maintenance mode status - definition: - arguments: [] - options: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: module:disable - usage: - - module:disable [-f|--force] [--all] [-c|--clear-static-content] [--magento-init-params - MAGENTO-INIT-PARAMS] [--] [<module>...] - description: Disables specified modules - help: Disables specified modules - definition: - arguments: - module: - name: module - is_required: false - is_array: true - description: Name of the module - default: [] - options: - force: - name: "--force" - shortcut: "-f" - accept_value: false - is_value_required: false - is_multiple: false - description: Bypass dependencies check - default: false - all: - name: "--all" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable all modules - default: false - clear-static-content: - name: "--clear-static-content" - shortcut: "-c" - accept_value: false - is_value_required: false - is_multiple: false - description: Clear generated static view files. Necessary, if the module(s) - have static view files - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: module:enable - usage: - - module:enable [-f|--force] [--all] [-c|--clear-static-content] [--magento-init-params - MAGENTO-INIT-PARAMS] [--] [<module>...] - description: Enables specified modules - help: Enables specified modules - definition: - arguments: - module: - name: module - is_required: false - is_array: true - description: Name of the module - default: [] - options: - force: - name: "--force" - shortcut: "-f" - accept_value: false - is_value_required: false - is_multiple: false - description: Bypass dependencies check - default: false - all: - name: "--all" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Enable all modules - default: false - clear-static-content: - name: "--clear-static-content" - shortcut: "-c" - accept_value: false - is_value_required: false - is_multiple: false - description: Clear generated static view files. Necessary, if the module(s) - have static view files - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: module:status - usage: - - module:status [--enabled] [--disabled] [--magento-init-params MAGENTO-INIT-PARAMS] - [--] [<module>] - description: Displays status of modules - help: Displays status of modules - definition: - arguments: - module: - name: module - is_required: false - is_array: false - description: Optional module name - default: - options: - enabled: - name: "--enabled" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Print only enabled modules - default: false - disabled: - name: "--disabled" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Print only disabled modules - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: module:uninstall - usage: - - module:uninstall [-r|--remove-data] [--backup-code] [--backup-media] [--backup-db] - [--non-composer] [-c|--clear-static-content] [--magento-init-params MAGENTO-INIT-PARAMS] - [--] <module>... - description: Uninstalls modules installed by composer - help: Uninstalls modules installed by composer - definition: - arguments: - module: - name: module - is_required: true - is_array: true - description: Name of the module - default: [] - options: - remove-data: - name: "--remove-data" - shortcut: "-r" - accept_value: false - is_value_required: false - is_multiple: false - description: Remove data installed by module(s) - default: false - backup-code: - name: "--backup-code" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Take code and configuration files backup (excluding temporary - files) - default: false - backup-media: - name: "--backup-media" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Take media backup - default: false - backup-db: - name: "--backup-db" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Take complete database backup - default: false - non-composer: - name: "--non-composer" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: All modules, that will be past here will be non composer based - default: false - clear-static-content: - name: "--clear-static-content" - shortcut: "-c" - accept_value: false - is_value_required: false - is_multiple: false - description: Clear generated static view files. Necessary, if the module(s) - have static view files - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: newrelic:create:deploy-marker - usage: - - newrelic:create:deploy-marker <message> <change_log> [<user>] - description: Check the deploy queue for entries and create an appropriate deploy - marker. - help: Check the deploy queue for entries and create an appropriate deploy marker. - definition: - arguments: - message: - name: message - is_required: true - is_array: false - description: Deploy Message? - default: - change_log: - name: change_log - is_required: true - is_array: false - description: Change Log? - default: - user: - name: user - is_required: false - is_array: false - description: Deployment User - default: - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: queue:consumers:list - usage: - - queue:consumers:list - description: List of MessageQueue consumers - help: This command shows list of MessageQueue consumers. - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: queue:consumers:start - usage: - - queue:consumers:start [--max-messages MAX-MESSAGES] [--batch-size BATCH-SIZE] - [--area-code AREA-CODE] [--pid-file-path PID-FILE-PATH] [--] <consumer> - description: Start MessageQueue consumer - help: |- - This command starts MessageQueue consumer by its name. - - To start consumer which will process all queued messages and terminate execution: - - <comment>bin/magento queue:consumers:start someConsumer</comment> - - To specify the number of messages which should be processed by consumer before its termination: - - <comment>bin/magento queue:consumers:start someConsumer --max-messages=50</comment> - - To specify the number of messages per batch for the batch consumer: - - <comment>bin/magento queue:consumers:start someConsumer --batch-size=500</comment> - - To specify the preferred area: - - <comment>bin/magento queue:consumers:start someConsumer --area-code='adminhtml'</comment> - - To save PID enter path: - - <comment>bin/magento queue:consumers:start someConsumer --pid-file-path='/var/someConsumer.pid'</comment> - definition: - arguments: - consumer: - name: consumer - is_required: true - is_array: false - description: The name of the consumer to be started. - default: - options: - max-messages: - name: "--max-messages" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The number of messages to be processed by the consumer before - process termination. If not specified - terminate after processing all queued - messages. - default: - batch-size: - name: "--batch-size" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The number of messages per batch. Applicable for the batch consumer - only. - default: - area-code: - name: "--area-code" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The preferred area (global, adminhtml, etc...) default is global. - default: - pid-file-path: - name: "--pid-file-path" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The file path for saving PID - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: sampledata:deploy - usage: - - sampledata:deploy [--no-update] - description: Deploy sample data modules for composer-based Magento installations - help: Deploy sample data modules for composer-based Magento installations - definition: - arguments: [] - options: - no-update: - name: "--no-update" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Update composer.json without executing composer update - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: sampledata:remove - usage: - - sampledata:remove [--no-update] - description: Remove all sample data packages from composer.json - help: Remove all sample data packages from composer.json - definition: - arguments: [] - options: - no-update: - name: "--no-update" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Update composer.json without executing composer update - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: sampledata:reset - usage: - - sampledata:reset - description: Reset all sample data modules for re-installation - help: Reset all sample data modules for re-installation - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:backup - usage: - - setup:backup [--code] [--media] [--db] [--magento-init-params MAGENTO-INIT-PARAMS] - description: Takes backup of Magento Application code base, media and database - help: Takes backup of Magento Application code base, media and database - definition: - arguments: [] - options: - code: - name: "--code" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Take code and configuration files backup (excluding temporary - files) - default: false - media: - name: "--media" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Take media backup - default: false - db: - name: "--db" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Take complete database backup - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:config:set - usage: - - setup:config:set [--amqp-host AMQP-HOST] [--amqp-port AMQP-PORT] [--amqp-user - AMQP-USER] [--amqp-password AMQP-PASSWORD] [--amqp-virtualhost AMQP-VIRTUALHOST] - [--amqp-ssl AMQP-SSL] [--amqp-ssl-options AMQP-SSL-OPTIONS] [--enable-debug-logging - ENABLE-DEBUG-LOGGING] [--enable-syslog-logging ENABLE-SYSLOG-LOGGING] [--backend-frontname - BACKEND-FRONTNAME] [--key KEY] [--db-host DB-HOST] [--db-name DB-NAME] [--db-user - DB-USER] [--db-engine DB-ENGINE] [--db-password DB-PASSWORD] [--db-prefix DB-PREFIX] - [--db-model DB-MODEL] [--db-init-statements DB-INIT-STATEMENTS] [-s|--skip-db-validation] - [--http-cache-hosts HTTP-CACHE-HOSTS] [--session-save SESSION-SAVE] [--session-save-redis-host - SESSION-SAVE-REDIS-HOST] [--session-save-redis-port SESSION-SAVE-REDIS-PORT] [--session-save-redis-password - SESSION-SAVE-REDIS-PASSWORD] [--session-save-redis-timeout SESSION-SAVE-REDIS-TIMEOUT] - [--session-save-redis-persistent-id SESSION-SAVE-REDIS-PERSISTENT-ID] [--session-save-redis-db - SESSION-SAVE-REDIS-DB] [--session-save-redis-compression-threshold SESSION-SAVE-REDIS-COMPRESSION-THRESHOLD] - [--session-save-redis-compression-lib SESSION-SAVE-REDIS-COMPRESSION-LIB] [--session-save-redis-log-level - SESSION-SAVE-REDIS-LOG-LEVEL] [--session-save-redis-max-concurrency SESSION-SAVE-REDIS-MAX-CONCURRENCY] - [--session-save-redis-break-after-frontend SESSION-SAVE-REDIS-BREAK-AFTER-FRONTEND] - [--session-save-redis-break-after-adminhtml SESSION-SAVE-REDIS-BREAK-AFTER-ADMINHTML] - [--session-save-redis-first-lifetime SESSION-SAVE-REDIS-FIRST-LIFETIME] [--session-save-redis-bot-first-lifetime - SESSION-SAVE-REDIS-BOT-FIRST-LIFETIME] [--session-save-redis-bot-lifetime SESSION-SAVE-REDIS-BOT-LIFETIME] - [--session-save-redis-disable-locking SESSION-SAVE-REDIS-DISABLE-LOCKING] [--session-save-redis-min-lifetime - SESSION-SAVE-REDIS-MIN-LIFETIME] [--session-save-redis-max-lifetime SESSION-SAVE-REDIS-MAX-LIFETIME] - [--session-save-redis-sentinel-master SESSION-SAVE-REDIS-SENTINEL-MASTER] [--session-save-redis-sentinel-servers - SESSION-SAVE-REDIS-SENTINEL-SERVERS] [--session-save-redis-sentinel-verify-master - SESSION-SAVE-REDIS-SENTINEL-VERIFY-MASTER] [--session-save-redis-sentinel-connect-retires - SESSION-SAVE-REDIS-SENTINEL-CONNECT-RETIRES] [--cache-backend CACHE-BACKEND] [--cache-backend-redis-server - CACHE-BACKEND-REDIS-SERVER] [--cache-backend-redis-db CACHE-BACKEND-REDIS-DB] - [--cache-backend-redis-port CACHE-BACKEND-REDIS-PORT] [--cache-backend-redis-password - CACHE-BACKEND-REDIS-PASSWORD] [--cache-backend-redis-compress-data CACHE-BACKEND-REDIS-COMPRESS-DATA] - [--cache-backend-redis-compression-lib CACHE-BACKEND-REDIS-COMPRESSION-LIB] [--cache-id-prefix - CACHE-ID-PREFIX] [--page-cache PAGE-CACHE] [--page-cache-redis-server PAGE-CACHE-REDIS-SERVER] - [--page-cache-redis-db PAGE-CACHE-REDIS-DB] [--page-cache-redis-port PAGE-CACHE-REDIS-PORT] - [--page-cache-redis-password PAGE-CACHE-REDIS-PASSWORD] [--page-cache-redis-compress-data - PAGE-CACHE-REDIS-COMPRESS-DATA] [--page-cache-redis-compression-lib PAGE-CACHE-REDIS-COMPRESSION-LIB] - [--page-cache-id-prefix PAGE-CACHE-ID-PREFIX] [--lock-provider LOCK-PROVIDER] - [--lock-db-prefix LOCK-DB-PREFIX] [--lock-zookeeper-host LOCK-ZOOKEEPER-HOST] - [--lock-zookeeper-path LOCK-ZOOKEEPER-PATH] [--lock-file-path LOCK-FILE-PATH] - [--magento-init-params MAGENTO-INIT-PARAMS] - description: Creates or modifies the deployment configuration - help: Creates or modifies the deployment configuration - definition: - arguments: [] - options: - amqp-host: - name: "--amqp-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server host - default: '' - amqp-port: - name: "--amqp-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server port - default: '5672' - amqp-user: - name: "--amqp-user" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server username - default: '' - amqp-password: - name: "--amqp-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server password - default: '' - amqp-virtualhost: - name: "--amqp-virtualhost" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp virtualhost - default: "/" - amqp-ssl: - name: "--amqp-ssl" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp SSL - default: '' - amqp-ssl-options: - name: "--amqp-ssl-options" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp SSL Options (JSON) - default: '' - enable-debug-logging: - name: "--enable-debug-logging" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Enable debug logging - default: - enable-syslog-logging: - name: "--enable-syslog-logging" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Enable syslog logging - default: - backend-frontname: - name: "--backend-frontname" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Backend frontname (will be autogenerated if missing) - default: - key: - name: "--key" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Encryption key - default: - db-host: - name: "--db-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server host - default: - db-name: - name: "--db-name" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database name - default: - db-user: - name: "--db-user" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server username - default: - db-engine: - name: "--db-engine" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server engine - default: innodb - db-password: - name: "--db-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server password - default: - db-prefix: - name: "--db-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database table prefix - default: - db-model: - name: "--db-model" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database type - default: mysql4 - db-init-statements: - name: "--db-init-statements" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database initial set of commands - default: SET NAMES utf8; - skip-db-validation: - name: "--skip-db-validation" - shortcut: "-s" - accept_value: false - is_value_required: false - is_multiple: false - description: If specified, then db connection validation will be skipped - default: false - http-cache-hosts: - name: "--http-cache-hosts" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: http Cache hosts - default: - session-save: - name: "--session-save" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Session save handler - default: - session-save-redis-host: - name: "--session-save-redis-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Fully qualified host name, IP address, or absolute path if using - UNIX sockets - default: - session-save-redis-port: - name: "--session-save-redis-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server listen port - default: - session-save-redis-password: - name: "--session-save-redis-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server password - default: - session-save-redis-timeout: - name: "--session-save-redis-timeout" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Connection timeout, in seconds - default: - session-save-redis-persistent-id: - name: "--session-save-redis-persistent-id" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Unique string to enable persistent connections - default: - session-save-redis-db: - name: "--session-save-redis-db" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis database number - default: - session-save-redis-compression-threshold: - name: "--session-save-redis-compression-threshold" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis compression threshold - default: - session-save-redis-compression-lib: - name: "--session-save-redis-compression-lib" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis compression library. Values: gzip (default), lzf, lz4, - snappy - default: - session-save-redis-log-level: - name: "--session-save-redis-log-level" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Redis log level. Values: 0 (least verbose) to 7 (most verbose)' - default: - session-save-redis-max-concurrency: - name: "--session-save-redis-max-concurrency" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Maximum number of processes that can wait for a lock on one session - default: - session-save-redis-break-after-frontend: - name: "--session-save-redis-break-after-frontend" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Number of seconds to wait before trying to break a lock for frontend - session - default: - session-save-redis-break-after-adminhtml: - name: "--session-save-redis-break-after-adminhtml" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Number of seconds to wait before trying to break a lock for Admin - session - default: - session-save-redis-first-lifetime: - name: "--session-save-redis-first-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lifetime, in seconds, of session for non-bots on the first write - (use 0 to disable) - default: - session-save-redis-bot-first-lifetime: - name: "--session-save-redis-bot-first-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lifetime, in seconds, of session for bots on the first write - (use 0 to disable) - default: - session-save-redis-bot-lifetime: - name: "--session-save-redis-bot-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lifetime of session for bots on subsequent writes (use 0 to disable) - default: - session-save-redis-disable-locking: - name: "--session-save-redis-disable-locking" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis disable locking. Values: false (default), true - default: - session-save-redis-min-lifetime: - name: "--session-save-redis-min-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis min session lifetime, in seconds - default: - session-save-redis-max-lifetime: - name: "--session-save-redis-max-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis max session lifetime, in seconds - default: - session-save-redis-sentinel-master: - name: "--session-save-redis-sentinel-master" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis Sentinel master - default: - session-save-redis-sentinel-servers: - name: "--session-save-redis-sentinel-servers" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis Sentinel servers, comma separated - default: - session-save-redis-sentinel-verify-master: - name: "--session-save-redis-sentinel-verify-master" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Redis Sentinel verify master. Values: false (default), true' - default: - session-save-redis-sentinel-connect-retires: - name: "--session-save-redis-sentinel-connect-retires" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis Sentinel connect retries. - default: - cache-backend: - name: "--cache-backend" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default cache handler - default: - cache-backend-redis-server: - name: "--cache-backend-redis-server" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server - default: - cache-backend-redis-db: - name: "--cache-backend-redis-db" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database number for the cache - default: - cache-backend-redis-port: - name: "--cache-backend-redis-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server listen port - default: - cache-backend-redis-password: - name: "--cache-backend-redis-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server password - default: - cache-backend-redis-compress-data: - name: "--cache-backend-redis-compress-data" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Set to 0 to disable compression (default is 1, enabled) - default: - cache-backend-redis-compression-lib: - name: "--cache-backend-redis-compression-lib" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Compression lib to use [snappy,lzf,l4z,zstd,gzip] (leave blank - to determine automatically) - default: - cache-id-prefix: - name: "--cache-id-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: ID prefix for cache keys - default: - page-cache: - name: "--page-cache" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default cache handler - default: - page-cache-redis-server: - name: "--page-cache-redis-server" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server - default: - page-cache-redis-db: - name: "--page-cache-redis-db" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database number for the cache - default: - page-cache-redis-port: - name: "--page-cache-redis-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server listen port - default: - page-cache-redis-password: - name: "--page-cache-redis-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server password - default: - page-cache-redis-compress-data: - name: "--page-cache-redis-compress-data" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Set to 1 to compress the full page cache (use 0 to disable) - default: - page-cache-redis-compression-lib: - name: "--page-cache-redis-compression-lib" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Compression library to use [snappy,lzf,l4z,zstd,gzip] (leave - blank to determine automatically) - default: - page-cache-id-prefix: - name: "--page-cache-id-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: ID prefix for cache keys - default: - lock-provider: - name: "--lock-provider" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lock provider name - default: db - lock-db-prefix: - name: "--lock-db-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Installation specific lock prefix to avoid lock conflicts - default: - lock-zookeeper-host: - name: "--lock-zookeeper-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Host and port to connect to Zookeeper cluster. For example: - 127.0.0.1:2181' - default: - lock-zookeeper-path: - name: "--lock-zookeeper-path" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'The path where Zookeeper will save locks. The default path is: - /magento/locks' - default: - lock-file-path: - name: "--lock-file-path" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The path where file locks will be saved. - default: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:cron:run - usage: - - setup:cron:run [--magento-init-params MAGENTO-INIT-PARAMS] - description: Runs cron job scheduled for setup application - help: Runs cron job scheduled for setup application - definition: - arguments: [] - options: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:db-data:upgrade - usage: - - setup:db-data:upgrade [--magento-init-params MAGENTO-INIT-PARAMS] - description: Installs and upgrades data in the DB - help: Installs and upgrades data in the DB - definition: - arguments: [] - options: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:db-declaration:generate-patch - usage: - - setup:db-declaration:generate-patch [--revertable [REVERTABLE]] [--type [TYPE]] - [--] <module> <patch> - description: Generate patch and put it in specific folder. - help: Generate patch and put it in specific folder. - definition: - arguments: - module: - name: module - is_required: true - is_array: false - description: Module name - default: - patch: - name: patch - is_required: true - is_array: false - description: Patch name - default: - options: - revertable: - name: "--revertable" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Check whether patch is revertable or not. - default: false - type: - name: "--type" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Find out what type of patch should be generated. - default: data - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:db-declaration:generate-whitelist - usage: - - setup:db-declaration:generate-whitelist [--module-name [MODULE-NAME]] - description: Generate whitelist of tables and columns that are allowed to be edited - by declaration installer - help: Generate whitelist of tables and columns that are allowed to be edited by - declaration installer - definition: - arguments: [] - options: - module-name: - name: "--module-name" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Name of the module where whitelist will be generated - default: all - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:db-schema:upgrade - usage: - - setup:db-schema:upgrade [--convert-old-scripts [CONVERT-OLD-SCRIPTS]] [--magento-init-params - MAGENTO-INIT-PARAMS] - description: Installs and upgrades the DB schema - help: Installs and upgrades the DB schema - definition: - arguments: [] - options: - convert-old-scripts: - name: "--convert-old-scripts" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Allows to convert old scripts (InstallSchema, UpgradeSchema) - to db_schema.xml format - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:db:status - usage: - - setup:db:status [--magento-init-params MAGENTO-INIT-PARAMS] - description: Checks if DB schema or data requires upgrade - help: Checks if DB schema or data requires upgrade - definition: - arguments: [] - options: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:di:compile - usage: - - setup:di:compile - description: Generates DI configuration and all missing classes that can be auto-generated - help: Generates DI configuration and all missing classes that can be auto-generated - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:install - usage: - - setup:install [--amqp-host AMQP-HOST] [--amqp-port AMQP-PORT] [--amqp-user AMQP-USER] - [--amqp-password AMQP-PASSWORD] [--amqp-virtualhost AMQP-VIRTUALHOST] [--amqp-ssl - AMQP-SSL] [--amqp-ssl-options AMQP-SSL-OPTIONS] [--enable-debug-logging ENABLE-DEBUG-LOGGING] - [--enable-syslog-logging ENABLE-SYSLOG-LOGGING] [--backend-frontname BACKEND-FRONTNAME] - [--key KEY] [--db-host DB-HOST] [--db-name DB-NAME] [--db-user DB-USER] [--db-engine - DB-ENGINE] [--db-password DB-PASSWORD] [--db-prefix DB-PREFIX] [--db-model DB-MODEL] - [--db-init-statements DB-INIT-STATEMENTS] [-s|--skip-db-validation] [--http-cache-hosts - HTTP-CACHE-HOSTS] [--session-save SESSION-SAVE] [--session-save-redis-host SESSION-SAVE-REDIS-HOST] - [--session-save-redis-port SESSION-SAVE-REDIS-PORT] [--session-save-redis-password - SESSION-SAVE-REDIS-PASSWORD] [--session-save-redis-timeout SESSION-SAVE-REDIS-TIMEOUT] - [--session-save-redis-persistent-id SESSION-SAVE-REDIS-PERSISTENT-ID] [--session-save-redis-db - SESSION-SAVE-REDIS-DB] [--session-save-redis-compression-threshold SESSION-SAVE-REDIS-COMPRESSION-THRESHOLD] - [--session-save-redis-compression-lib SESSION-SAVE-REDIS-COMPRESSION-LIB] [--session-save-redis-log-level - SESSION-SAVE-REDIS-LOG-LEVEL] [--session-save-redis-max-concurrency SESSION-SAVE-REDIS-MAX-CONCURRENCY] - [--session-save-redis-break-after-frontend SESSION-SAVE-REDIS-BREAK-AFTER-FRONTEND] - [--session-save-redis-break-after-adminhtml SESSION-SAVE-REDIS-BREAK-AFTER-ADMINHTML] - [--session-save-redis-first-lifetime SESSION-SAVE-REDIS-FIRST-LIFETIME] [--session-save-redis-bot-first-lifetime - SESSION-SAVE-REDIS-BOT-FIRST-LIFETIME] [--session-save-redis-bot-lifetime SESSION-SAVE-REDIS-BOT-LIFETIME] - [--session-save-redis-disable-locking SESSION-SAVE-REDIS-DISABLE-LOCKING] [--session-save-redis-min-lifetime - SESSION-SAVE-REDIS-MIN-LIFETIME] [--session-save-redis-max-lifetime SESSION-SAVE-REDIS-MAX-LIFETIME] - [--session-save-redis-sentinel-master SESSION-SAVE-REDIS-SENTINEL-MASTER] [--session-save-redis-sentinel-servers - SESSION-SAVE-REDIS-SENTINEL-SERVERS] [--session-save-redis-sentinel-verify-master - SESSION-SAVE-REDIS-SENTINEL-VERIFY-MASTER] [--session-save-redis-sentinel-connect-retires - SESSION-SAVE-REDIS-SENTINEL-CONNECT-RETIRES] [--cache-backend CACHE-BACKEND] [--cache-backend-redis-server - CACHE-BACKEND-REDIS-SERVER] [--cache-backend-redis-db CACHE-BACKEND-REDIS-DB] - [--cache-backend-redis-port CACHE-BACKEND-REDIS-PORT] [--cache-backend-redis-password - CACHE-BACKEND-REDIS-PASSWORD] [--cache-backend-redis-compress-data CACHE-BACKEND-REDIS-COMPRESS-DATA] - [--cache-backend-redis-compression-lib CACHE-BACKEND-REDIS-COMPRESSION-LIB] [--cache-id-prefix - CACHE-ID-PREFIX] [--page-cache PAGE-CACHE] [--page-cache-redis-server PAGE-CACHE-REDIS-SERVER] - [--page-cache-redis-db PAGE-CACHE-REDIS-DB] [--page-cache-redis-port PAGE-CACHE-REDIS-PORT] - [--page-cache-redis-password PAGE-CACHE-REDIS-PASSWORD] [--page-cache-redis-compress-data - PAGE-CACHE-REDIS-COMPRESS-DATA] [--page-cache-redis-compression-lib PAGE-CACHE-REDIS-COMPRESSION-LIB] - [--page-cache-id-prefix PAGE-CACHE-ID-PREFIX] [--lock-provider LOCK-PROVIDER] - [--lock-db-prefix LOCK-DB-PREFIX] [--lock-zookeeper-host LOCK-ZOOKEEPER-HOST] - [--lock-zookeeper-path LOCK-ZOOKEEPER-PATH] [--lock-file-path LOCK-FILE-PATH] - [--base-url BASE-URL] [--language LANGUAGE] [--timezone TIMEZONE] [--currency - CURRENCY] [--use-rewrites USE-REWRITES] [--use-secure USE-SECURE] [--base-url-secure - BASE-URL-SECURE] [--use-secure-admin USE-SECURE-ADMIN] [--admin-use-security-key - ADMIN-USE-SECURITY-KEY] [--admin-user [ADMIN-USER]] [--admin-password [ADMIN-PASSWORD]] - [--admin-email [ADMIN-EMAIL]] [--admin-firstname [ADMIN-FIRSTNAME]] [--admin-lastname - [ADMIN-LASTNAME]] [--cleanup-database] [--sales-order-increment-prefix SALES-ORDER-INCREMENT-PREFIX] - [--use-sample-data] [--enable-modules [ENABLE-MODULES]] [--disable-modules [DISABLE-MODULES]] - [--convert-old-scripts [CONVERT-OLD-SCRIPTS]] [-i|--interactive] [--safe-mode - [SAFE-MODE]] [--data-restore [DATA-RESTORE]] [--dry-run [DRY-RUN]] [--magento-init-params - MAGENTO-INIT-PARAMS] - description: Installs the Magento application - help: Installs the Magento application - definition: - arguments: [] - options: - amqp-host: - name: "--amqp-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server host - default: '' - amqp-port: - name: "--amqp-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server port - default: '5672' - amqp-user: - name: "--amqp-user" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server username - default: '' - amqp-password: - name: "--amqp-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp server password - default: '' - amqp-virtualhost: - name: "--amqp-virtualhost" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp virtualhost - default: "/" - amqp-ssl: - name: "--amqp-ssl" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp SSL - default: '' - amqp-ssl-options: - name: "--amqp-ssl-options" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Amqp SSL Options (JSON) - default: '' - enable-debug-logging: - name: "--enable-debug-logging" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Enable debug logging - default: - enable-syslog-logging: - name: "--enable-syslog-logging" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Enable syslog logging - default: - backend-frontname: - name: "--backend-frontname" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Backend frontname (will be autogenerated if missing) - default: - key: - name: "--key" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Encryption key - default: - db-host: - name: "--db-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server host - default: - db-name: - name: "--db-name" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database name - default: - db-user: - name: "--db-user" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server username - default: - db-engine: - name: "--db-engine" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server engine - default: innodb - db-password: - name: "--db-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database server password - default: - db-prefix: - name: "--db-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database table prefix - default: - db-model: - name: "--db-model" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database type - default: mysql4 - db-init-statements: - name: "--db-init-statements" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database initial set of commands - default: SET NAMES utf8; - skip-db-validation: - name: "--skip-db-validation" - shortcut: "-s" - accept_value: false - is_value_required: false - is_multiple: false - description: If specified, then db connection validation will be skipped - default: false - http-cache-hosts: - name: "--http-cache-hosts" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: http Cache hosts - default: - session-save: - name: "--session-save" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Session save handler - default: - session-save-redis-host: - name: "--session-save-redis-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Fully qualified host name, IP address, or absolute path if using - UNIX sockets - default: - session-save-redis-port: - name: "--session-save-redis-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server listen port - default: - session-save-redis-password: - name: "--session-save-redis-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server password - default: - session-save-redis-timeout: - name: "--session-save-redis-timeout" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Connection timeout, in seconds - default: - session-save-redis-persistent-id: - name: "--session-save-redis-persistent-id" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Unique string to enable persistent connections - default: - session-save-redis-db: - name: "--session-save-redis-db" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis database number - default: - session-save-redis-compression-threshold: - name: "--session-save-redis-compression-threshold" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis compression threshold - default: - session-save-redis-compression-lib: - name: "--session-save-redis-compression-lib" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis compression library. Values: gzip (default), lzf, lz4, - snappy - default: - session-save-redis-log-level: - name: "--session-save-redis-log-level" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Redis log level. Values: 0 (least verbose) to 7 (most verbose)' - default: - session-save-redis-max-concurrency: - name: "--session-save-redis-max-concurrency" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Maximum number of processes that can wait for a lock on one session - default: - session-save-redis-break-after-frontend: - name: "--session-save-redis-break-after-frontend" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Number of seconds to wait before trying to break a lock for frontend - session - default: - session-save-redis-break-after-adminhtml: - name: "--session-save-redis-break-after-adminhtml" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Number of seconds to wait before trying to break a lock for Admin - session - default: - session-save-redis-first-lifetime: - name: "--session-save-redis-first-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lifetime, in seconds, of session for non-bots on the first write - (use 0 to disable) - default: - session-save-redis-bot-first-lifetime: - name: "--session-save-redis-bot-first-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lifetime, in seconds, of session for bots on the first write - (use 0 to disable) - default: - session-save-redis-bot-lifetime: - name: "--session-save-redis-bot-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lifetime of session for bots on subsequent writes (use 0 to disable) - default: - session-save-redis-disable-locking: - name: "--session-save-redis-disable-locking" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis disable locking. Values: false (default), true - default: - session-save-redis-min-lifetime: - name: "--session-save-redis-min-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis min session lifetime, in seconds - default: - session-save-redis-max-lifetime: - name: "--session-save-redis-max-lifetime" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis max session lifetime, in seconds - default: - session-save-redis-sentinel-master: - name: "--session-save-redis-sentinel-master" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis Sentinel master - default: - session-save-redis-sentinel-servers: - name: "--session-save-redis-sentinel-servers" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis Sentinel servers, comma separated - default: - session-save-redis-sentinel-verify-master: - name: "--session-save-redis-sentinel-verify-master" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Redis Sentinel verify master. Values: false (default), true' - default: - session-save-redis-sentinel-connect-retires: - name: "--session-save-redis-sentinel-connect-retires" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis Sentinel connect retries. - default: - cache-backend: - name: "--cache-backend" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default cache handler - default: - cache-backend-redis-server: - name: "--cache-backend-redis-server" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server - default: - cache-backend-redis-db: - name: "--cache-backend-redis-db" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database number for the cache - default: - cache-backend-redis-port: - name: "--cache-backend-redis-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server listen port - default: - cache-backend-redis-password: - name: "--cache-backend-redis-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server password - default: - cache-backend-redis-compress-data: - name: "--cache-backend-redis-compress-data" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Set to 0 to disable compression (default is 1, enabled) - default: - cache-backend-redis-compression-lib: - name: "--cache-backend-redis-compression-lib" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Compression lib to use [snappy,lzf,l4z,zstd,gzip] (leave blank - to determine automatically) - default: - cache-id-prefix: - name: "--cache-id-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: ID prefix for cache keys - default: - page-cache: - name: "--page-cache" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default cache handler - default: - page-cache-redis-server: - name: "--page-cache-redis-server" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server - default: - page-cache-redis-db: - name: "--page-cache-redis-db" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Database number for the cache - default: - page-cache-redis-port: - name: "--page-cache-redis-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server listen port - default: - page-cache-redis-password: - name: "--page-cache-redis-password" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Redis server password - default: - page-cache-redis-compress-data: - name: "--page-cache-redis-compress-data" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Set to 1 to compress the full page cache (use 0 to disable) - default: - page-cache-redis-compression-lib: - name: "--page-cache-redis-compression-lib" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Compression library to use [snappy,lzf,l4z,zstd,gzip] (leave - blank to determine automatically) - default: - page-cache-id-prefix: - name: "--page-cache-id-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: ID prefix for cache keys - default: - lock-provider: - name: "--lock-provider" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Lock provider name - default: db - lock-db-prefix: - name: "--lock-db-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Installation specific lock prefix to avoid lock conflicts - default: - lock-zookeeper-host: - name: "--lock-zookeeper-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Host and port to connect to Zookeeper cluster. For example: - 127.0.0.1:2181' - default: - lock-zookeeper-path: - name: "--lock-zookeeper-path" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'The path where Zookeeper will save locks. The default path is: - /magento/locks' - default: - lock-file-path: - name: "--lock-file-path" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The path where file locks will be saved. - default: - base-url: - name: "--base-url" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: URL the store is supposed to be available at. Deprecated, use - config:set with path web/unsecure/base_url - default: - language: - name: "--language" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default language code. Deprecated, use config:set with path general/locale/code - default: - timezone: - name: "--timezone" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default time zone code. Deprecated, use config:set with path - general/locale/timezone - default: - currency: - name: "--currency" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default currency code. Deprecated, use config:set with path currency/options/base, - currency/options/default and currency/options/allow - default: - use-rewrites: - name: "--use-rewrites" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Use rewrites. Deprecated, use config:set with path web/seo/use_rewrites - default: - use-secure: - name: "--use-secure" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Use secure URLs. Enable this option only if SSL is available. - Deprecated, use config:set with path web/secure/use_in_frontend - default: - base-url-secure: - name: "--base-url-secure" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Base URL for SSL connection. Deprecated, use config:set with - path web/secure/base_url - default: - use-secure-admin: - name: "--use-secure-admin" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Run admin interface with SSL. Deprecated, use config:set with - path web/secure/use_in_adminhtml - default: - admin-use-security-key: - name: "--admin-use-security-key" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Whether to use a "security key" feature in Magento Admin URLs - and forms. Deprecated, use config:set with path admin/security/use_form_key - default: - admin-user: - name: "--admin-user" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Admin user - default: - admin-password: - name: "--admin-password" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Admin password - default: - admin-email: - name: "--admin-email" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Admin email - default: - admin-firstname: - name: "--admin-firstname" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Admin first name - default: - admin-lastname: - name: "--admin-lastname" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Admin last name - default: - cleanup-database: - name: "--cleanup-database" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Cleanup the database before installation - default: false - sales-order-increment-prefix: - name: "--sales-order-increment-prefix" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Sales order number prefix - default: - use-sample-data: - name: "--use-sample-data" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Use sample data - default: false - enable-modules: - name: "--enable-modules" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: List of comma-separated module names. That must be included during - installation. Available magic param "all". - default: - disable-modules: - name: "--disable-modules" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: List of comma-separated module names. That must be avoided during - installation. Available magic param "all". - default: - convert-old-scripts: - name: "--convert-old-scripts" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Allows to convert old scripts (InstallSchema, UpgradeSchema) - to db_schema.xml format - default: false - interactive: - name: "--interactive" - shortcut: "-i" - accept_value: false - is_value_required: false - is_multiple: false - description: Interactive Magento installation - default: false - safe-mode: - name: "--safe-mode" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Safe installation of Magento with dumps on destructive operations, - like column removal - default: - data-restore: - name: "--data-restore" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Restore removed data from dumps - default: - dry-run: - name: "--dry-run" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Magento Installation will be run in dry-run mode - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:performance:generate-fixtures - usage: - - setup:performance:generate-fixtures [-s|--skip-reindex] [--] <profile> - description: Generates fixtures - help: Generates fixtures - definition: - arguments: - profile: - name: profile - is_required: true - is_array: false - description: Path to profile configuration file - default: - options: - skip-reindex: - name: "--skip-reindex" - shortcut: "-s" - accept_value: false - is_value_required: false - is_multiple: false - description: Skip reindex - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:rollback - usage: - - setup:rollback [-c|--code-file CODE-FILE] [-m|--media-file MEDIA-FILE] [-d|--db-file - DB-FILE] [--magento-init-params MAGENTO-INIT-PARAMS] - description: Rolls back Magento Application codebase, media and database - help: Rolls back Magento Application codebase, media and database - definition: - arguments: [] - options: - code-file: - name: "--code-file" - shortcut: "-c" - accept_value: true - is_value_required: true - is_multiple: false - description: Basename of the code backup file in var/backups - default: - media-file: - name: "--media-file" - shortcut: "-m" - accept_value: true - is_value_required: true - is_multiple: false - description: Basename of the media backup file in var/backups - default: - db-file: - name: "--db-file" - shortcut: "-d" - accept_value: true - is_value_required: true - is_multiple: false - description: Basename of the db backup file in var/backups - default: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:static-content:deploy - usage: - - setup:static-content:deploy [-f|--force] [-s|--strategy [STRATEGY]] [-a|--area - [AREA]] [--exclude-area [EXCLUDE-AREA]] [-t|--theme [THEME]] [--exclude-theme - [EXCLUDE-THEME]] [-l|--language [LANGUAGE]] [--exclude-language [EXCLUDE-LANGUAGE]] - [-j|--jobs [JOBS]] [--max-execution-time [MAX-EXECUTION-TIME]] [--symlink-locale] - [--content-version CONTENT-VERSION] [--refresh-content-version-only] [--no-javascript] - [--no-css] [--no-less] [--no-images] [--no-fonts] [--no-html] [--no-misc] [--no-html-minify] - [--] [<languages>...] - description: Deploys static view files - help: Deploys static view files - definition: - arguments: - languages: - name: languages - is_required: false - is_array: true - description: Space-separated list of ISO-639 language codes for which to output - static view files. - default: [] - options: - force: - name: "--force" - shortcut: "-f" - accept_value: false - is_value_required: false - is_multiple: false - description: Deploy files in any mode. - default: false - strategy: - name: "--strategy" - shortcut: "-s" - accept_value: true - is_value_required: false - is_multiple: false - description: Deploy files using specified strategy. - default: quick - area: - name: "--area" - shortcut: "-a" - accept_value: true - is_value_required: false - is_multiple: true - description: Generate files only for the specified areas. - default: - - all - exclude-area: - name: "--exclude-area" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: true - description: Do not generate files for the specified areas. - default: - - none - theme: - name: "--theme" - shortcut: "-t" - accept_value: true - is_value_required: false - is_multiple: true - description: Generate static view files for only the specified themes. - default: - - all - exclude-theme: - name: "--exclude-theme" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: true - description: Do not generate files for the specified themes. - default: - - none - language: - name: "--language" - shortcut: "-l" - accept_value: true - is_value_required: false - is_multiple: true - description: Generate files only for the specified languages. - default: - - all - exclude-language: - name: "--exclude-language" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: true - description: Do not generate files for the specified languages. - default: - - none - jobs: - name: "--jobs" - shortcut: "-j" - accept_value: true - is_value_required: false - is_multiple: false - description: Enable parallel processing using the specified number of jobs. - default: 0 - max-execution-time: - name: "--max-execution-time" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: The maximum expected execution time of deployment static process - (in seconds). - default: 400 - symlink-locale: - name: "--symlink-locale" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Create symlinks for the files of those locales, which are passed - for deployment, but have no customizations. - default: false - content-version: - name: "--content-version" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Custom version of static content can be used if running deployment - on multiple nodes to ensure that static content version is identical and - caching works properly. - default: - refresh-content-version-only: - name: "--refresh-content-version-only" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Refreshing the version of static content only can be used to - refresh static content in browser cache and CDN cache. - default: false - no-javascript: - name: "--no-javascript" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not deploy JavaScript files. - default: false - no-css: - name: "--no-css" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not deploy CSS files. - default: false - no-less: - name: "--no-less" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not deploy LESS files. - default: false - no-images: - name: "--no-images" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not deploy images. - default: false - no-fonts: - name: "--no-fonts" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not deploy font files. - default: false - no-html: - name: "--no-html" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not deploy HTML files. - default: false - no-misc: - name: "--no-misc" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not deploy files of other types (.md, .jbf, .csv, etc.). - default: false - no-html-minify: - name: "--no-html-minify" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Do not minify HTML files. - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:store-config:set - usage: - - setup:store-config:set [--base-url BASE-URL] [--language LANGUAGE] [--timezone - TIMEZONE] [--currency CURRENCY] [--use-rewrites USE-REWRITES] [--use-secure USE-SECURE] - [--base-url-secure BASE-URL-SECURE] [--use-secure-admin USE-SECURE-ADMIN] [--admin-use-security-key - ADMIN-USE-SECURITY-KEY] [--magento-init-params MAGENTO-INIT-PARAMS] - description: Installs the store configuration. Deprecated since 2.2.0. Use config:set - instead - help: Installs the store configuration. Deprecated since 2.2.0. Use config:set instead - definition: - arguments: [] - options: - base-url: - name: "--base-url" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: URL the store is supposed to be available at. Deprecated, use - config:set with path web/unsecure/base_url - default: - language: - name: "--language" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default language code. Deprecated, use config:set with path general/locale/code - default: - timezone: - name: "--timezone" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default time zone code. Deprecated, use config:set with path - general/locale/timezone - default: - currency: - name: "--currency" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Default currency code. Deprecated, use config:set with path currency/options/base, - currency/options/default and currency/options/allow - default: - use-rewrites: - name: "--use-rewrites" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Use rewrites. Deprecated, use config:set with path web/seo/use_rewrites - default: - use-secure: - name: "--use-secure" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Use secure URLs. Enable this option only if SSL is available. - Deprecated, use config:set with path web/secure/use_in_frontend - default: - base-url-secure: - name: "--base-url-secure" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Base URL for SSL connection. Deprecated, use config:set with - path web/secure/base_url - default: - use-secure-admin: - name: "--use-secure-admin" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Run admin interface with SSL. Deprecated, use config:set with - path web/secure/use_in_adminhtml - default: - admin-use-security-key: - name: "--admin-use-security-key" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Whether to use a "security key" feature in Magento Admin URLs - and forms. Deprecated, use config:set with path admin/security/use_form_key - default: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:uninstall - usage: - - setup:uninstall [--magento-init-params MAGENTO-INIT-PARAMS] - description: Uninstalls the Magento application - help: Uninstalls the Magento application - definition: - arguments: [] - options: - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: setup:upgrade - usage: - - setup:upgrade [--keep-generated] [--convert-old-scripts [CONVERT-OLD-SCRIPTS]] - [--safe-mode [SAFE-MODE]] [--data-restore [DATA-RESTORE]] [--dry-run [DRY-RUN]] - [--magento-init-params MAGENTO-INIT-PARAMS] - description: Upgrades the Magento application, DB data, and schema - help: Upgrades the Magento application, DB data, and schema - definition: - arguments: [] - options: - keep-generated: - name: "--keep-generated" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Prevents generated files from being deleted. We discourage using - this option except when deploying to production. Consult your system integrator - or administrator for more information. - default: false - convert-old-scripts: - name: "--convert-old-scripts" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Allows to convert old scripts (InstallSchema, UpgradeSchema) - to db_schema.xml format - default: false - safe-mode: - name: "--safe-mode" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Safe installation of Magento with dumps on destructive operations, - like column removal - default: - data-restore: - name: "--data-restore" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Restore removed data from dumps - default: - dry-run: - name: "--dry-run" - shortcut: '' - accept_value: true - is_value_required: false - is_multiple: false - description: Magento Installation will be run in dry-run mode - default: false - magento-init-params: - name: "--magento-init-params" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: 'Add to any command to customize Magento initialization parameters - For example: "MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/example.com&MAGE_DIRS[cache][path]=/var/tmp/cache"' - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: store:list - usage: - - store:list - description: Displays the list of stores - help: Displays the list of stores - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: store:website:list - usage: - - store:website:list - description: Displays the list of websites - help: Displays the list of websites - definition: - arguments: [] - options: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: theme:uninstall - usage: - - theme:uninstall [--backup-code] [-c|--clear-static-content] [--] <theme>... - description: Uninstalls theme - help: Uninstalls theme - definition: - arguments: - theme: - name: theme - is_required: true - is_array: true - description: Path of the theme. Theme path should be specified as full path - which is area/vendor/name. For example, frontend/Magento/blank - default: [] - options: - backup-code: - name: "--backup-code" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Take code backup (excluding temporary files) - default: false - clear-static-content: - name: "--clear-static-content" - shortcut: "-c" - accept_value: false - is_value_required: false - is_multiple: false - description: Clear generated static view files. - default: false - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -- name: varnish:vcl:generate - usage: - - varnish:vcl:generate [--access-list ACCESS-LIST] [--backend-host BACKEND-HOST] - [--backend-port BACKEND-PORT] [--export-version EXPORT-VERSION] [--grace-period - GRACE-PERIOD] [--output-file OUTPUT-FILE] - description: Generates Varnish VCL and echos it to the command line - help: Generates Varnish VCL and echos it to the command line - definition: - arguments: [] - options: - access-list: - name: "--access-list" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: true - description: IPs access list that can purge Varnish - default: - - localhost - backend-host: - name: "--backend-host" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Host of the web backend - default: localhost - backend-port: - name: "--backend-port" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Port of the web backend - default: 8080 - export-version: - name: "--export-version" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: The version of Varnish file - default: '4' - grace-period: - name: "--grace-period" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Grace period in seconds - default: 300 - output-file: - name: "--output-file" - shortcut: '' - accept_value: true - is_value_required: true - is_multiple: false - description: Path to the file to write vcl - default: - help: - name: "--help" - shortcut: "-h" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this help message - default: false - quiet: - name: "--quiet" - shortcut: "-q" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not output any message - default: false - verbose: - name: "--verbose" - shortcut: "-v|-vv|-vvv" - accept_value: false - is_value_required: false - is_multiple: false - description: 'Increase the verbosity of messages: 1 for normal output, 2 for - more verbose output and 3 for debug' - default: false - version: - name: "--version" - shortcut: "-V" - accept_value: false - is_value_required: false - is_multiple: false - description: Display this application version - default: false - ansi: - name: "--ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Force ANSI output - default: false - no-ansi: - name: "--no-ansi" - shortcut: '' - accept_value: false - is_value_required: false - is_multiple: false - description: Disable ANSI output - default: false - no-interaction: - name: "--no-interaction" - shortcut: "-n" - accept_value: false - is_value_required: false - is_multiple: false - description: Do not ask any interactive question - default: false - hidden: false -namespaces: -- id: _global - commands: - - help - - list -- id: admin - commands: - - admin:user:create - - admin:user:unlock -- id: app - commands: - - app:config:dump - - app:config:import - - app:config:status -- id: cache - commands: - - cache:clean - - cache:disable - - cache:enable - - cache:flush - - cache:status -- id: catalog - commands: - - catalog:images:resize - - catalog:product:attributes:cleanup -- id: config - commands: - - config:sensitive:set - - config:set - - config:show -- id: cron - commands: - - cron:install - - cron:remove - - cron:run -- id: customer - commands: - - customer:hash:upgrade -- id: deploy - commands: - - deploy:mode:set - - deploy:mode:show -- id: dev - commands: - - dev:di:info - - dev:profiler:disable - - dev:profiler:enable - - dev:query-log:disable - - dev:query-log:enable - - dev:source-theme:deploy - - dev:template-hints:disable - - dev:template-hints:enable - - dev:tests:run - - dev:urn-catalog:generate - - dev:xml:convert -- id: encryption - commands: - - encryption:payment-data:update -- id: i18n - commands: - - i18n:collect-phrases - - i18n:pack - - i18n:uninstall -- id: indexer - commands: - - indexer:info - - indexer:reindex - - indexer:reset - - indexer:set-dimensions-mode - - indexer:set-mode - - indexer:show-dimensions-mode - - indexer:show-mode - - indexer:status -- id: info - commands: - - info:adminuri - - info:backups:list - - info:currency:list - - info:dependencies:show-framework - - info:dependencies:show-modules - - info:dependencies:show-modules-circular - - info:language:list - - info:timezone:list -- id: maintenance - commands: - - maintenance:allow-ips - - maintenance:disable - - maintenance:enable - - maintenance:status -- id: module - commands: - - module:disable - - module:enable - - module:status - - module:uninstall -- id: newrelic - commands: - - newrelic:create:deploy-marker -- id: queue - commands: - - queue:consumers:list - - queue:consumers:start -- id: sampledata - commands: - - sampledata:deploy - - sampledata:remove - - sampledata:reset -- id: setup - commands: - - setup:backup - - setup:config:set - - setup:cron:run - - setup:db-data:upgrade - - setup:db-declaration:generate-patch - - setup:db-declaration:generate-whitelist - - setup:db-schema:upgrade - - setup:db:status - - setup:di:compile - - setup:install - - setup:performance:generate-fixtures - - setup:rollback - - setup:static-content:deploy - - setup:store-config:set - - setup:uninstall - - setup:upgrade -- id: store - commands: - - store:list - - store:website:list -- id: theme - commands: - - theme:uninstall -- id: varnish - commands: - - varnish:vcl:generate From b4bdc73fa35d865fdc460487ca217f8848930183 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Wed, 21 Aug 2019 21:17:51 +0000 Subject: [PATCH 321/841] Removed customer_subscribe.php and customer_subscribe_rollback.php --- .../GraphQl/Customer/CreateCustomerTest.php | 2 +- .../Customer/_files/customer_subscribe.php | 14 -------------- .../_files/customer_subscribe_rollback.php | 16 ---------------- 3 files changed, 1 insertion(+), 31 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php delete mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index d6c7e590639f0..9654be4f5e3cc 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -276,7 +276,7 @@ public function testCreateCustomerIfNameEmpty() } /** - * @magentoApiDataFixture Magento/Customer/_files/customer_subscribe.php + * @magentoConfigFixture default_store newsletter/general/active 0 * @throws \Exception */ public function testCreateCustomerSubscribed() diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php deleted file mode 100644 index c776eeab48ded..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -$resourceConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Config\Model\ResourceModel\Config::class); - -$resourceConfig->saveConfig( - 'newsletter/general/active', - false, - 'default', - 0 -); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php deleted file mode 100644 index d8773e97b5db7..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 -declare(strict_types=1); - -use Magento\Framework\App\Config\Storage\WriterInterface; -use Magento\TestFramework\Helper\Bootstrap; - -$objectManager = Bootstrap::getObjectManager(); - -/** @var Writer $configWriter */ -$configWriter = $objectManager->create(WriterInterface::class); -$configWriter->delete('newsletter/general/active'); From 57225503d2c176d63f5938c79e86c95dc6f4907b Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Wed, 21 Aug 2019 16:22:15 -0500 Subject: [PATCH 322/841] MC-18945: Reading deprecated annotation in schema - added PR builds fixes --- .../Config/GraphQlDeprecatedAnnotationReaderTest.php | 12 +++++------- .../GraphQlReader/MetaReader/FieldMetaReader.php | 12 ++++++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php index a70bd2cacdb35..d41ff92807f85 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php @@ -194,7 +194,7 @@ enumValues(includeDeprecated: true) { //Checks to make sure that the given description exists in the expectedOutput array $this->assertTrue( - array_key_exists( + array_key_exists( array_search( 'Comment for SortEnum', array_column($expectedOutput, 'description') @@ -207,22 +207,20 @@ enumValues(includeDeprecated: true) { $fieldsArray = $expectedOutput[0]['fields']; $enumValuesArray = $expectedOutput[1]['enumValues']; - foreach($fieldsArray as $field){ - if ( $field['isDeprecated'] === true){ + foreach ($fieldsArray as $field) { + if ($field['isDeprecated'] === true) { $typeDeprecatedReason [] = $field['deprecationReason']; } } $this->assertNotEmpty($typeDeprecatedReason); $this->assertContains('Deprecated url_path test', $typeDeprecatedReason); - foreach($enumValuesArray as $enumValue){ - if ( $enumValue['isDeprecated'] === true){ + foreach ($enumValuesArray as $enumValue) { + if ($enumValue['isDeprecated'] === true) { $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; } } - $this->assertNotEmpty($enumValueDeprecatedReason); $this->assertContains('Deprecated SortEnum Value test',$enumValueDeprecatedReason); - } } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index 2c8ac12347a9a..32dffde56f1eb 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -99,10 +99,14 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra $result['arguments'][$argumentName]['defaultValue'] = $argumentMeta->defaultValue; } $typeMeta = $argumentMeta->getType(); - $result['arguments'][$argumentName] = array_merge( - $result['arguments'][$argumentName], - $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) - ); + foreach ($result['arguments'][$argumentName] as $argument) { + $argument = $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER); + return $argument; + } +// $result['arguments'][$argumentName] = array_merge_recursive( +// $result['arguments'][$argumentName], +// $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) +// ); if ($this->docReader->read($argumentMeta->astNode->directives)) { $result['arguments'][$argumentName]['description'] = From 4c768ce22bb753009ccd7e0af5eee8d0a797a5c4 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Wed, 21 Aug 2019 13:52:25 -0500 Subject: [PATCH 323/841] MC-19296: PayPal Express Guest Checkout Issue --- .../Paypal/Model/SmartButtonConfig.php | 18 ++++++- .../Test/Unit/Model/SmartButtonConfigTest.php | 47 ++++++++++++++----- .../Unit/Model/_files/expected_config.php | 15 ++++-- .../js/in-context/product-express-checkout.js | 11 ++++- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/Paypal/Model/SmartButtonConfig.php b/app/code/Magento/Paypal/Model/SmartButtonConfig.php index c491f2978e560..ede9cacf25d40 100644 --- a/app/code/Magento/Paypal/Model/SmartButtonConfig.php +++ b/app/code/Magento/Paypal/Model/SmartButtonConfig.php @@ -7,7 +7,10 @@ namespace Magento\Paypal\Model; +use Magento\Checkout\Helper\Data; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Locale\ResolverInterface; +use Magento\Store\Model\ScopeInterface; /** * Smart button config @@ -34,21 +37,29 @@ class SmartButtonConfig */ private $allowedFunding; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * @param ResolverInterface $localeResolver * @param ConfigFactory $configFactory + * @param ScopeConfigInterface $scopeConfig * @param array $defaultStyles * @param array $allowedFunding */ public function __construct( ResolverInterface $localeResolver, ConfigFactory $configFactory, + ScopeConfigInterface $scopeConfig, $defaultStyles = [], $allowedFunding = [] ) { $this->localeResolver = $localeResolver; $this->config = $configFactory->create(); $this->config->setMethod(Config::METHOD_EXPRESS); + $this->scopeConfig = $scopeConfig; $this->defaultStyles = $defaultStyles; $this->allowedFunding = $allowedFunding; } @@ -61,6 +72,10 @@ public function __construct( */ public function getConfig(string $page): array { + $isGuestCheckoutAllowed = $this->scopeConfig->isSetFlag( + Data::XML_PATH_GUEST_CHECKOUT, + ScopeInterface::SCOPE_STORE + ); return [ 'merchantId' => $this->config->getValue('merchant_id'), 'environment' => ((int)$this->config->getValue('sandbox_flag') ? 'sandbox' : 'production'), @@ -68,7 +83,8 @@ public function getConfig(string $page): array 'allowedFunding' => $this->getAllowedFunding($page), 'disallowedFunding' => $this->getDisallowedFunding(), 'styles' => $this->getButtonStyles($page), - 'isVisibleOnProductPage' => (int)$this->config->getValue('visible_on_product') + 'isVisibleOnProductPage' => $this->config->getValue('visible_on_product'), + 'isGuestCheckoutAllowed' => $isGuestCheckoutAllowed ]; } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php index ed62efe36c472..6298df3ee8e23 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php @@ -7,10 +7,15 @@ namespace Magento\Paypal\Test\Unit\Model; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Paypal\Model\Config; use Magento\Paypal\Model\SmartButtonConfig; use Magento\Framework\Locale\ResolverInterface; use Magento\Paypal\Model\ConfigFactory; +/** + * Class SmartButtonConfigTest + */ class SmartButtonConfigTest extends \PHPUnit\Framework\TestCase { /** @@ -31,9 +36,15 @@ class SmartButtonConfigTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->localeResolverMock = $this->getMockForAbstractClass(ResolverInterface::class); - $this->configMock = $this->getMockBuilder(\Magento\Paypal\Model\Config::class) + $this->configMock = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); + + /** @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject $scopeConfigMock */ + $scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); + $scopeConfigMock->method('isSetFlag') + ->willReturn(true); + /** @var \PHPUnit_Framework_MockObject_MockObject $configFactoryMock */ $configFactoryMock = $this->getMockBuilder(ConfigFactory::class) ->disableOriginalConstructor() @@ -43,12 +54,15 @@ protected function setUp() $this->model = new SmartButtonConfig( $this->localeResolverMock, $configFactoryMock, + $scopeConfigMock, $this->getDefaultStyles(), $this->getAllowedFundings() ); } /** + * Tests config. + * * @param string $page * @param string $locale * @param string $disallowedFundings @@ -78,22 +92,29 @@ public function testGetConfig( array $expected = [] ) { $this->localeResolverMock->expects($this->any())->method('getLocale')->willReturn($locale); - $this->configMock->expects($this->any())->method('getValue')->will($this->returnValueMap([ - ['merchant_id', null, 'merchant'], - ['sandbox_flag', null, true], - ['disable_funding_options', null, $disallowedFundings], - ["{$page}_page_button_customize", null, $isCustomize], - ["{$page}_page_button_layout", null, $layout], - ["{$page}_page_button_size", null, $size], - ["{$page}_page_button_color", null, $color], - ["{$page}_page_button_shape", null, $shape], - ["{$page}_page_button_label", null, $label], - [$page . '_page_button_' . $installmentPeriodLocale . '_installment_period', null, $installmentPeriodLabel] - ])); + $this->configMock->method('getValue')->will( + $this->returnValueMap( + [ + ['merchant_id', null, 'merchant'], + ['sandbox_flag', null, true], + ['disable_funding_options', null, $disallowedFundings], + ["{$page}_page_button_customize", null, $isCustomize], + ["{$page}_page_button_layout", null, $layout], + ["{$page}_page_button_size", null, $size], + ["{$page}_page_button_color", null, $color], + ["{$page}_page_button_shape", null, $shape], + ["{$page}_page_button_label", null, $label], + [$page . '_page_button_' . $installmentPeriodLocale . '_installment_period', null, $installmentPeriodLabel] + ] + ) + ); self::assertEquals($expected, $this->model->getConfig($page)); } + /** + * @return array + */ public function getConfigDataProvider() { return include __DIR__ . '/_files/expected_config.php'; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php index 1442642a324b9..478607f9956e6 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php @@ -32,7 +32,8 @@ 'label' => 'installment', 'installmentperiod' => 0 ], - 'isVisibleOnProductPage' => 0 + 'isVisibleOnProductPage' => 0, + 'isGuestCheckoutAllowed' => true ] ], 'checkout' => [ @@ -61,7 +62,8 @@ 'label' => 'installment', 'installmentperiod' => 0 ], - 'isVisibleOnProductPage' => 0 + 'isVisibleOnProductPage' => 0, + 'isGuestCheckoutAllowed' => true ] ], 'mini_cart' => [ @@ -89,7 +91,8 @@ 'shape' => 'rect', 'label' => 'paypal' ], - 'isVisibleOnProductPage' => 0 + 'isVisibleOnProductPage' => 0, + 'isGuestCheckoutAllowed' => true ] ], 'mini_cart' => [ @@ -117,7 +120,8 @@ 'shape' => 'rect', 'label' => 'paypal' ], - 'isVisibleOnProductPage' => 0 + 'isVisibleOnProductPage' => 0, + 'isGuestCheckoutAllowed' => true ] ], 'product' => [ @@ -145,7 +149,8 @@ 'shape' => 'rect', 'label' => 'paypal', ], - 'isVisibleOnProductPage' => 0 + 'isVisibleOnProductPage' => 0, + 'isGuestCheckoutAllowed' => true ] ] ]; diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js index f4adaae06a112..b2be5fe2b3d2b 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js @@ -21,15 +21,22 @@ define([ /** @inheritdoc */ initialize: function (config, element) { var cart = customerData.get('cart'), - customer = customerData.get('customer'); + customer = customerData.get('customer'), + isGuestCheckoutAllowed; this._super(); + isGuestCheckoutAllowed = cart().isGuestCheckoutAllowed; + + if (typeof isGuestCheckoutAllowed === 'undefined') { + isGuestCheckoutAllowed = config.clientConfig.isGuestCheckoutAllowed; + } + if (config.clientConfig.isVisibleOnProductPage) { this.renderPayPalButtons(element); } - this.declinePayment = !customer().firstname && !cart().isGuestCheckoutAllowed; + this.declinePayment = !customer().firstname && !isGuestCheckoutAllowed; return this; }, From caa10122dd60bb7ecbd9327974a3cbe49c7af5f2 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Wed, 21 Aug 2019 17:04:58 -0500 Subject: [PATCH 324/841] MC-18945: Reading deprecated annotation in schema - added PR builds new fixes --- .../GraphQlReader/MetaReader/FieldMetaReader.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index 32dffde56f1eb..d2374e92ce255 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -99,14 +99,11 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra $result['arguments'][$argumentName]['defaultValue'] = $argumentMeta->defaultValue; } $typeMeta = $argumentMeta->getType(); - foreach ($result['arguments'][$argumentName] as $argument) { - $argument = $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER); - return $argument; - } -// $result['arguments'][$argumentName] = array_merge_recursive( -// $result['arguments'][$argumentName], -// $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) -// ); + + $result['arguments'][$argumentName] = array_merge_recursive( + $result['arguments'][$argumentName], + $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) + ); if ($this->docReader->read($argumentMeta->astNode->directives)) { $result['arguments'][$argumentName]['description'] = From c1e34125e9bdb17ca3ab82ea9a422754c56e365d Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Wed, 21 Aug 2019 18:19:45 -0500 Subject: [PATCH 325/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix failing tests and removing duplicates --- .../GraphQl/Catalog/ProductSearchTest.php | 123 +++++------------- .../GraphQl/Catalog/ProductViewTest.php | 2 +- .../GraphQl/VariablesSupportQueryTest.php | 6 +- 3 files changed, 36 insertions(+), 95 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index bcd0ffedf542f..a565ac656124a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -1110,60 +1110,6 @@ public function testQuerySortByPriceDESCWithDefaultPageSize() $this->assertEquals(1, $response['products']['page_info']['current_page']); } - /** - * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php - */ - public function testProductQueryUsingFromAndToFilterInput() - { - $query - = <<<QUERY -{ - products( - filter: { - price:{ - from:"5" to:"20" - } - } - ) { - total_count - items { - attribute_set_id - sku - name - price { - minimalPrice { - amount { - value - currency - } - } - maximalPrice { - amount { - value - currency - } - } - } - type_id - ...on PhysicalProductInterface { - weight - } - } - } -} -QUERY; - - $response = $this->graphQlQuery($query); - $this->assertEquals(2, $response['products']['total_count']); - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $product1 = $productRepository->get('simple1'); - $product2 = $productRepository->get('simple2'); - $filteredProducts = [$product2, $product1]; - - $this->assertProductItemsWithMaximalAndMinimalPriceCheck($filteredProducts, $response); - } - /** * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php */ @@ -1230,7 +1176,7 @@ public function testProductBasicFullTextSearchQuery() /** * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php */ - public function testProductsThatMatchWithPricesFromList() + public function testProductsThatMatchWithinASpecificPriceRange() { $query =<<<QUERY @@ -1238,14 +1184,14 @@ public function testProductsThatMatchWithPricesFromList() products( filter: { - price:{in:["10","20"]} + price:{from:"5" to: "20"} } pageSize:4 currentPage:1 sort: { - name:DESC + price:DESC } ) { @@ -1254,6 +1200,18 @@ public function testProductsThatMatchWithPricesFromList() attribute_set_id sku price { + minimalPrice { + amount { + value + currency + } + } + maximalPrice { + amount { + value + currency + } + } regularPrice { amount { value @@ -1284,27 +1242,7 @@ public function testProductsThatMatchWithPricesFromList() $prod1 = $productRepository->get('simple2'); $prod2 = $productRepository->get('simple1'); $filteredProducts = [$prod1, $prod2]; - $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); - foreach ($productItemsInResponse as $itemIndex => $itemArray) { - $this->assertNotEmpty($itemArray); - $this->assertResponseFields( - $productItemsInResponse[$itemIndex][0], - ['attribute_set_id' => $filteredProducts[$itemIndex]->getAttributeSetId(), - 'sku' => $filteredProducts[$itemIndex]->getSku(), - 'name' => $filteredProducts[$itemIndex]->getName(), - 'price' => [ - 'regularPrice' => [ - 'amount' => [ - 'value' => $filteredProducts[$itemIndex]->getPrice(), - 'currency' => 'USD' - ] - ] - ], - 'type_id' =>$filteredProducts[$itemIndex]->getTypeId(), - 'weight' => $filteredProducts[$itemIndex]->getWeight() - ] - ); - } + $this->assertProductItemsWithPriceCheck($filteredProducts, $response); } /** @@ -1320,21 +1258,17 @@ public function testQueryFilterNoMatchingItems() { products( filter: - { - special_price:{lt:"15"} - price:{lt:"50"} - weight:{gt:"4"} - or: - { - sku:{like:"simple%"} - name:{like:"%simple%"} - } + { + price:{from:"50"} + sku:{like:"simple%"} + name:{like:"simple%"} + } pageSize:2 currentPage:1 sort: { - sku:ASC + position:ASC } ) { @@ -1384,7 +1318,7 @@ public function testQueryPageOutOfBoundException() products( filter: { - price:{eq:"10"} + price:{to:"10"} } pageSize:2 currentPage:2 @@ -1605,7 +1539,7 @@ private function assertProductItems(array $filteredProducts, array $actualRespon } } - private function assertProductItemsWithMaximalAndMinimalPriceCheck(array $filteredProducts, array $actualResponse) + private function assertProductItemsWithPriceCheck(array $filteredProducts, array $actualResponse) { $productItemsInResponse = array_map(null, $actualResponse['products']['items'], $filteredProducts); @@ -1628,7 +1562,14 @@ private function assertProductItemsWithMaximalAndMinimalPriceCheck(array $filter 'value' => $filteredProducts[$itemIndex]->getSpecialPrice(), 'currency' => 'USD' ] - ] + ], + 'regularPrice' => [ + 'amount' => [ + 'value' => $filteredProducts[$itemIndex]->getPrice(), + 'currency' => 'USD' + ] + ] + ], 'type_id' =>$filteredProducts[$itemIndex]->getTypeId(), 'weight' => $filteredProducts[$itemIndex]->getWeight() diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index e11e2e8d108c2..5990211f1e47d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -592,7 +592,7 @@ public function testProductPrices() $secondProductSku = 'simple-156'; $query = <<<QUERY { - products(filter: {min_price: {gt: "100.0"}, max_price: {gt: "150.0", lt: "250.0"}}) + products(filter: {price: {from: "150.0", to: "250.0"}}) { items { attribute_set_id diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/VariablesSupportQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/VariablesSupportQueryTest.php index 7448b165fc234..3221026871bc8 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/VariablesSupportQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/VariablesSupportQueryTest.php @@ -33,7 +33,7 @@ public function testQueryObjectVariablesSupport() $query = <<<'QUERY' -query GetProductsQuery($pageSize: Int, $filterInput: ProductFilterInput, $priceSort: SortEnum) { +query GetProductsQuery($pageSize: Int, $filterInput: ProductAttributeFilterInput, $priceSort: SortEnum) { products( pageSize: $pageSize filter: $filterInput @@ -58,8 +58,8 @@ public function testQueryObjectVariablesSupport() 'pageSize' => 1, 'priceSort' => 'ASC', 'filterInput' => [ - 'min_price' => [ - 'gt' => 150, + 'price' => [ + 'from' => 150, ], ], ]; From fe9d262f5998448fe63ecf5aecb7ac0a7dd0b92d Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Wed, 21 Aug 2019 20:24:59 -0400 Subject: [PATCH 326/841] Update tests for later version error messaging --- .../Magento/GraphQl/Customer/CreateCustomerAddressTest.php | 4 ++-- .../Magento/GraphQl/Customer/UpdateCustomerAddressTest.php | 4 ++-- .../Magento/GraphQl/Controller/GraphQlControllerTest.php | 2 +- .../Model/Resolver/Guest/PaypalExpressTokenTest.php | 4 ++-- .../Resolver/Guest/PaypalPayflowProTokenExceptionTest.php | 2 +- .../Model/Resolver/Guest/PaypalPayflowProTokenTest.php | 2 +- .../Model/Resolver/Guest/PlaceOrderWithHostedProTest.php | 4 ++-- .../Model/Resolver/Guest/PlaceOrderWithPayflowLinkTest.php | 2 +- .../Resolver/Guest/PlaceOrderWithPaymentsAdvancedTest.php | 4 ++-- .../Resolver/Guest/SetPaymentMethodAsPayflowLinkTest.php | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php index 47c4d3ad91cb6..203e9b5cb42e5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerAddressTest.php @@ -303,8 +303,8 @@ public function invalidInputDataProvider() { return [ ['', 'Syntax Error: Expected Name, found )'], - ['input: ""', 'Expected type CustomerAddressInput!, found "".'], - ['input: "foo"', 'Expected type CustomerAddressInput!, found "foo".'] + ['input: ""', 'requires type CustomerAddressInput!, found "".'], + ['input: "foo"', 'requires type CustomerAddressInput!, found "foo".'] ]; } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php index f2e82398df49b..9840236dc9896 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php @@ -306,8 +306,8 @@ public function invalidInputDataProvider() { return [ ['', '"input" value must be specified'], - ['input: ""', 'Expected type CustomerAddressInput, found ""'], - ['input: "foo"', 'Expected type CustomerAddressInput, found "foo"'] + ['input: ""', 'requires type CustomerAddressInput, found ""'], + ['input: "foo"', 'requires type CustomerAddressInput, found "foo"'] ]; } diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php index d0d746812ec44..91e1df21fafc8 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php @@ -251,7 +251,7 @@ public function testError() : void foreach ($outputResponse['errors'] as $error) { $this->assertEquals( \Magento\Framework\GraphQl\Exception\GraphQlInputException::EXCEPTION_CATEGORY, - $error['category'] + $error['extensions']['category'] ); if (isset($error['message'])) { $this->assertEquals($error['message'], 'Invalid entity_type specified: invalid'); diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalExpressTokenTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalExpressTokenTest.php index 3c7bd4a8c0bd3..24c64bce54438 100644 --- a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalExpressTokenTest.php +++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalExpressTokenTest.php @@ -136,7 +136,7 @@ public function testResolveWithPaypalError($paymentMethod): void $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } /** @@ -173,7 +173,7 @@ public function testResolveWithInvalidRedirectUrl($paymentMethod): void $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } /** diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenExceptionTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenExceptionTest.php index b5de225b7abbc..25a9a01c4c4c7 100644 --- a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenExceptionTest.php +++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenExceptionTest.php @@ -73,6 +73,6 @@ public function testResolveWithPaypalError(): void $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } } diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenTest.php index df7f80ccd35a8..248f5d297be32 100644 --- a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenTest.php +++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PaypalPayflowProTokenTest.php @@ -130,6 +130,6 @@ public function testResolveWithInvalidRedirectUrl(): void $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } } diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithHostedProTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithHostedProTest.php index 0920aa2eb36b8..a8136fda73c09 100644 --- a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithHostedProTest.php +++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithHostedProTest.php @@ -206,7 +206,7 @@ public function testOrderWithHostedProDeclined(): void $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } /** @@ -258,6 +258,6 @@ public function testSetPaymentMethodInvalidUrls() $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } } diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPayflowLinkTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPayflowLinkTest.php index c2ec5e6bddde3..f4fe3e7e60fd8 100644 --- a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPayflowLinkTest.php +++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPayflowLinkTest.php @@ -273,6 +273,6 @@ public function testResolveWithPayflowLinkDeclined(): void $expectedExceptionMessage, $actualError['message'] ); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } } diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPaymentsAdvancedTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPaymentsAdvancedTest.php index 7ef5db4a2ddab..a40a56be5faee 100644 --- a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPaymentsAdvancedTest.php +++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/PlaceOrderWithPaymentsAdvancedTest.php @@ -178,7 +178,7 @@ public function testResolvePaymentsAdvancedWithInvalidUrl(): void $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } /** @@ -230,7 +230,7 @@ public function testResolveWithPaymentAdvancedDeclined(): void $this->assertArrayHasKey('errors', $responseData); $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } /** diff --git a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/SetPaymentMethodAsPayflowLinkTest.php b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/SetPaymentMethodAsPayflowLinkTest.php index 7c23ec08af652..224159348ada4 100644 --- a/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/SetPaymentMethodAsPayflowLinkTest.php +++ b/dev/tests/integration/testsuite/Magento/PaypalGraphQl/Model/Resolver/Guest/SetPaymentMethodAsPayflowLinkTest.php @@ -168,6 +168,6 @@ public function testInvalidUrl(): void $expectedExceptionMessage = "Invalid Url."; $actualError = $responseData['errors'][0]; $this->assertEquals($expectedExceptionMessage, $actualError['message']); - $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['category']); + $this->assertEquals(GraphQlInputException::EXCEPTION_CATEGORY, $actualError['extensions']['category']); } } From f6f0aef0be6ff3324d7269ecb4439707e725da41 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Wed, 21 Aug 2019 21:11:15 -0400 Subject: [PATCH 327/841] Rename summary_count to total_quanity --- .../{CartSummaryCount.php => CartTotalQuantity.php} | 2 +- app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 2 +- ...aryCountTest.php => GetCartTotalQuantityTest.php} | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) rename app/code/Magento/QuoteGraphQl/Model/Resolver/{CartSummaryCount.php => CartTotalQuantity.php} (94%) rename dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/{GetCartSummaryCountTest.php => GetCartTotalQuantityTest.php} (83%) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartSummaryCount.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php similarity index 94% rename from app/code/Magento/QuoteGraphQl/Model/Resolver/CartSummaryCount.php rename to app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php index 6e06241ebe6c1..320a103c62108 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartSummaryCount.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php @@ -16,7 +16,7 @@ /** * @inheritdoc */ -class CartSummaryCount implements ResolverInterface +class CartTotalQuantity implements ResolverInterface { /** * @inheritdoc diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index f9e05f9b86084..b033d6d06a899 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -197,7 +197,7 @@ type Cart { available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods") selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod") prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices") - summary_count: Int! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartSummaryCount") + total_quantity: Int! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTotalQuantity") } interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartSummaryCountTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTotalQuantityTest.php similarity index 83% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartSummaryCountTest.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTotalQuantityTest.php index 2017ef734f8a6..aa7cb16ec1296 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartSummaryCountTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTotalQuantityTest.php @@ -11,9 +11,9 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; /** - * Test for getting summary count from cart query + * Test for getting total quantity from cart query */ -class GetCartSummaryCountTest extends GraphQlAbstract +class GetCartTotalQuantityTest extends GraphQlAbstract { /** * @var GetMaskedQuoteIdByReservedOrderId @@ -31,7 +31,7 @@ protected function setUp() * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testSetNewBillingAddress() + public function testGetTotalQuantity() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -41,8 +41,8 @@ public function testSetNewBillingAddress() self::assertArrayHasKey('cart', $response); $cart = $response['cart']; - self::assertArrayHasKey('summary_count', $cart); - self::assertEquals(2, $cart['summary_count']); + self::assertArrayHasKey('total_quantity', $cart); + self::assertEquals(2, $cart['total_quantity']); } /** @@ -56,7 +56,7 @@ private function getQuery(string $maskedQuoteId): string return <<<QUERY { cart(cart_id: "{$maskedQuoteId}") { - summary_count + total_quantity } } QUERY; From 9e12d9e90b9aebd9c0f6ba15013de88038a39c0a Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 22 Aug 2019 04:57:23 +0000 Subject: [PATCH 328/841] MC-18954: Nightly build jobs were failing lately after un-skipping tests in MC-5777 --- .../Model/Indexer/ReindexRuleProductTest.php | 47 +++++-------------- 1 file changed, 12 insertions(+), 35 deletions(-) diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php index ff566fa3cc774..a86ab736fb289 100644 --- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php +++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php @@ -89,17 +89,6 @@ public function testExecute() 6 => [$websiteId => 1], ]; - $ruleMock = $this->createMock(Rule::class); - $ruleMock->expects($this->once()) - ->method('getIsActive') - ->willReturn(true); - $ruleMock->expects($this->exactly(2)) - ->method('getWebsiteIds') - ->willReturn([$websiteId]); - $ruleMock->expects($this->once()) - ->method('getMatchingProductIds') - ->willReturn($productIds); - $this->tableSwapperMock->expects($this->once()) ->method('getWorkingTableName') ->with('catalogrule_product') @@ -118,30 +107,18 @@ public function testExecute() ->with('catalogrule_product_replica') ->willReturn('catalogrule_product_replica'); - $ruleMock->expects($this->once()) - ->method('getId') - ->willReturn(100); - $ruleMock->expects($this->once()) - ->method('getCustomerGroupIds') - ->willReturn([10]); - $ruleMock->expects($this->atLeastOnce()) - ->method('getFromDate') - ->willReturn('2017-06-21'); - $ruleMock->expects($this->atLeastOnce()) - ->method('getToDate') - ->willReturn('2017-06-30'); - $ruleMock->expects($this->once()) - ->method('getSortOrder') - ->willReturn(1); - $ruleMock->expects($this->once()) - ->method('getSimpleAction') - ->willReturn('simple_action'); - $ruleMock->expects($this->once()) - ->method('getDiscountAmount') - ->willReturn(43); - $ruleMock->expects($this->once()) - ->method('getStopRulesProcessing') - ->willReturn(true); + $ruleMock = $this->createMock(Rule::class); + $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true); + $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn([$websiteId]); + $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds); + $ruleMock->expects($this->once())->method('getId')->willReturn(100); + $ruleMock->expects($this->once())->method('getCustomerGroupIds')->willReturn([10]); + $ruleMock->expects($this->atLeastOnce())->method('getFromDate')->willReturn('2017-06-21'); + $ruleMock->expects($this->atLeastOnce())->method('getToDate')->willReturn('2017-06-30'); + $ruleMock->expects($this->once())->method('getSortOrder')->willReturn(1); + $ruleMock->expects($this->once())->method('getSimpleAction')->willReturn('simple_action'); + $ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43); + $ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true); $this->localeDateMock->expects($this->once()) ->method('getConfigTimezone') From 5d16cb1a5897adf439209c3c9cde1254cf3be20b Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Thu, 22 Aug 2019 10:02:20 +0400 Subject: [PATCH 329/841] MAGETWO-98748: Incorrect behavior in the category menu on the Storefront - Updated automated test script --- .../StorefrontCategoryHighlightedAndProductDisplayedTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml index 7d09cb419f312..c036712398bf9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="CheckCurrentCategoryIsHighlightedAndProductsDisplayed"> + <test name="StorefrontCategoryHighlightedAndProductDisplayedTest"> <annotations> <features value="Catalog"/> <stories value="Category"/> From d15db0ac45cdae53fb43802e358052c0b23b34f4 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Thu, 22 Aug 2019 10:20:24 +0300 Subject: [PATCH 330/841] MC-19399: Import of Customizable Option price does not respect the website scope --- .../Magento/CatalogImportExport/Model/Import/Product/Option.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index efd763ecfcf8e..4d8088a235402 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -1458,6 +1458,8 @@ protected function _collectOptionMainData( ) { if ($this->_isPriceGlobal) { $prices[$nextOptionId][Store::DEFAULT_STORE_ID] = $priceData; + } else { + $prices[$nextOptionId][$this->_rowStoreId] = $priceData; } } From e74a95cd59eb2f38baded3997007b9ba0c614ec9 Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Thu, 22 Aug 2019 11:14:49 +0200 Subject: [PATCH 331/841] MC-18221: Update composer lock --- composer.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index 3a4b14b2dd2d4..340fb386a3654 100644 --- a/composer.lock +++ b/composer.lock @@ -6545,9 +6545,9 @@ "authors": [ { "name": "Johannes Schmitt", + "role": "Developer of wrapped JMSSerializerBundle", "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh", - "role": "Developer of wrapped JMSSerializerBundle" + "homepage": "https://github.com/schmittjoh" } ], "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", From 43612445d8c9c70353618a23b39f9aab327bce57 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Thu, 22 Aug 2019 12:58:49 +0300 Subject: [PATCH 332/841] magento/magento2#23253: Added support for float numbers without leading zero. --- .../Magento/Ui/view/base/web/js/lib/validation/rules.js | 3 ++- .../code/Magento/Ui/base/js/lib/validation/rules.test.js | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index 078839ba3381c..3402d1d1df03b 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -651,7 +651,8 @@ define([ 'validate-number': [ function (value) { return utils.isEmptyNoTrim(value) || - !isNaN(utils.parseNumber(value)) && /^\s*-?\d{1,}(?:[.,|'|\s]\d{1,})*(?:[.,|'|\s]\d{2})?-?\s*$/.test(value); + !isNaN(utils.parseNumber(value)) && + /^\s*-?\d*(?:[.,|'|\s]\d+)*(?:[.,|'|\s]\d{2})?-?\s*$/.test(value); }, $.mage.__('Please enter a valid number in this field.') ], diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js index 976e43ad01dc6..91b07256d0796 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js @@ -58,6 +58,12 @@ define([ expect(rules['validate-number'].handler(value)).toBe(true); }); + it('Check on float without leading zero', function () { + var value = '.50'; + + expect(rules['validate-number'].handler(value)).toBe(true); + }); + it('Check on formatted float', function () { var value = '1,000,000.50'; @@ -81,7 +87,7 @@ define([ expect(rules['validate-number'].handler(value)).toBe(true); }); - + it('Check on not a number', function () { var value = 'string'; From a3441be22dec3382ba81ad968e67ca7f86344369 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Thu, 22 Aug 2019 13:09:47 +0300 Subject: [PATCH 333/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" --- .../OtherPayPalConfigurationActionGroup.xml | 21 ++++++++++++------- ...ayPalSolutionsEnabledAtTheSameTimeTest.xml | 2 -- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml index f6f2fc05c524d..acc7695fb1607 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml @@ -28,13 +28,20 @@ <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForPageLoad2"/> </actionGroup> - <actionGroup name="EnablePayPalSolution" extends="EnablePayPalConfiguration"> - <remove keyForRemoval="waitForOtherPayPalPaymentsSection"/> - <remove keyForRemoval="clickOtherPayPalPaymentsSection"/> - <remove keyForRemoval="seeAlertMessage"/> - <remove keyForRemoval="acceptEnablePopUp"/> - <remove keyForRemoval="saveConfig"/> - <remove keyForRemoval="waitForPageLoad2"/> + <actionGroup name="EnablePayPalSolution" > + <annotations> + <description>Expands the 'OTHER PAYPAL PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code.</description> + </annotations> + <arguments> + <argument name="payPalConfigType"/> + <argument name="countryCode" type="string" defaultValue="us"/> + </arguments> + <waitForElementVisible selector="{{OtherPayPalPaymentsConfigSection.expandTab(countryCode)}}" stepKey="waitForOtherPayPalPaymentsSection"/> + <conditionalClick selector="{{OtherPayPalPaymentsConfigSection.expandTab(countryCode)}}" dependentSelector="{{OtherPayPalPaymentsConfigSection.expandedTab(countryCode)}}" visible="false" stepKey="clickOtherPayPalPaymentsSection"/> + <waitForElementVisible selector="{{payPalConfigType.configureBtn(countryCode)}}" stepKey="waitForWPSExpressConfigureBtn"/> + <click selector="{{payPalConfigType.configureBtn(countryCode)}}" stepKey="clickWPSExpressConfigureBtn"/> + <waitForElementVisible selector="{{payPalConfigType.enableSolution(countryCode)}}" stepKey="waitForWPSExpressEnable"/> + <selectOption selector="{{payPalConfigType.enableSolution(countryCode)}}" userInput="Yes" stepKey="enableWPSExpressSolution"/> </actionGroup> <actionGroup name="CheckEnableOptionPayPalConfiguration"> <annotations> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml index 58904b1de796e..e1ab68f6de8e9 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml @@ -39,8 +39,6 @@ <!--Try to enable express checkout Solution--> <comment userInput="Try to enable express checkout Solution" stepKey="commentTryEnableExpressCheckout"/> <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> - <conditionalClick selector="{{OtherPayPalPaymentsConfigSection.expandTab('us')}}" dependentSelector="{{OtherPayPalPaymentsConfigSection.expandedTab('us')}}" visible="false" stepKey="clickOtherPayPalPaymentsSection"/> <actionGroup ref="EnablePayPalSolution" stepKey="enableExpressCheckout"> <argument name="payPalConfigType" value="WPSExpressConfigSection"/> <argument name="countryCode" value="us"/> From fc87a57fffa759631f527be2831406779cc56f37 Mon Sep 17 00:00:00 2001 From: Sudheer Kumar Gajjala <51822411+sudheer-gajjala@users.noreply.github.com> Date: Wed, 21 Aug 2019 13:35:59 +0530 Subject: [PATCH 334/841] added fix for the ticket #24210 https://github.com/magento/magento2/issues/24210 this is the url of the issue --- .../adminhtml/Magento/backend/web/js/theme.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/design/adminhtml/Magento/backend/web/js/theme.js b/app/design/adminhtml/Magento/backend/web/js/theme.js index 8e3b89dcf7e4e..39b364ea8553f 100644 --- a/app/design/adminhtml/Magento/backend/web/js/theme.js +++ b/app/design/adminhtml/Magento/backend/web/js/theme.js @@ -267,17 +267,18 @@ define('globalNavigation', [ if (subMenu.length) { e.preventDefault(); } - - menuItem.addClass('_show') - .siblings(menuItemSelector) - .removeClass('_show'); - - subMenu.attr('aria-expanded', 'true'); - closeBtn.on('click', close); - this.overlay.show(0).on('click', close); - this.menuLinks.last().off('blur'); + if ($(menuItem).hasClass('_show')) { + closeBtn.trigger('click'); + } else { + menuItem.addClass('_show') + .siblings(menuItemSelector) + .removeClass('_show'); + subMenu.attr('aria-expanded', 'true'); + this.overlay.show(0).on('click', close); + this.menuLinks.last().off('blur'); + } }, /** From 871a812bc1af402eb6f673097d2cddee611b0589 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 22 Aug 2019 14:06:17 +0300 Subject: [PATCH 335/841] MC-18194: Saved Credit Card Feature with Vault is not displaying proper information of card in order information page --- app/code/Magento/Vault/Model/Method/Vault.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index 2961dbfb4ec0d..91205fe7663ca 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -544,8 +544,10 @@ private function attachCreditCardInfo(OrderPaymentInterface $payment): void { $paymentToken = $payment->getExtensionAttributes() ->getVaultPaymentToken(); - $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); - $payment->addData($tokenDetails); + if ($paymentToken) { + $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); + $payment->addData($tokenDetails); + } } /** From ed1df5fb3610140f552e06ad43747dbbe3147bc7 Mon Sep 17 00:00:00 2001 From: rani-webkul <rani.priya4392webkul.com> Date: Thu, 22 Aug 2019 18:17:09 +0530 Subject: [PATCH 336/841] Resolved File type custom option value not showing properly in minicart #24236 --- .../view/frontend/web/template/minicart/item/default.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html index 41d442a76d510..32bbd66d13e68 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html @@ -44,7 +44,10 @@ <!-- ko if: Array.isArray(option.value) --> <span data-bind="html: option.value.join('<br>')"></span> <!-- /ko --> - <!-- ko ifnot: Array.isArray(option.value) --> + <!-- ko if: (!Array.isArray(option.value) && option.option_type == 'file') --> + <span data-bind="html: option.value"></span> + <!-- /ko --> + <!-- ko if: (!Array.isArray(option.value) && option.option_type != 'file') --> <span data-bind="text: option.value"></span> <!-- /ko --> </dd> From f77ce35c34a3f1d9dd96eb51d12a963ac1872b46 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 22 Aug 2019 15:47:25 +0300 Subject: [PATCH 337/841] MC-18194: Saved Credit Card Feature with Vault is not displaying proper information of card in order information page --- app/code/Magento/Vault/Model/Method/Vault.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index 91205fe7663ca..b5d998ee128f6 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -5,6 +5,7 @@ */ namespace Magento\Vault\Model\Method; +use Exception; use Magento\Framework\Event\ManagerInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Payment\Gateway\Command; @@ -546,7 +547,9 @@ private function attachCreditCardInfo(OrderPaymentInterface $payment): void ->getVaultPaymentToken(); if ($paymentToken) { $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); - $payment->addData($tokenDetails); + if ($tokenDetails && is_array($tokenDetails)) { + $payment->addData($tokenDetails); + } } } From 281a6a41d42da2f9fb06243cd1e1e153e0c7f87b Mon Sep 17 00:00:00 2001 From: rani-webkul <rani.priya4392webkul.com> Date: Thu, 22 Aug 2019 19:17:20 +0530 Subject: [PATCH 338/841] Resolved On cart page not able to check uploaded file of file type custom option #24239 --- .../Checkout/view/frontend/templates/cart/item/default.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml index 77dde1eab482a..86c9a357af23c 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml @@ -50,7 +50,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima <?php if (isset($_formatedOptionValue['full_view'])) :?> <?= $block->escapeHtml($_formatedOptionValue['full_view']) ?> <?php else :?> - <?= $block->escapeHtml($_formatedOptionValue['value'], ['span']) ?> + <?= $block->escapeHtml($_formatedOptionValue['value'], ['span', 'a']) ?> <?php endif; ?> </dd> <?php endforeach; ?> From cd9894a69efbb4c6e12e887f100a46953c5e574f Mon Sep 17 00:00:00 2001 From: Tan Sezer <t.sezer@youwe.nl> Date: Thu, 22 Aug 2019 16:26:10 +0200 Subject: [PATCH 339/841] If the serializer return null then to set empty array --- .../Product/Initialization/CleanConfigurationTmpImages.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php index 1d088d0ac043f..b3959d794bbd3 100644 --- a/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php +++ b/app/code/Magento/ConfigurableProduct/Plugin/Product/Initialization/CleanConfigurationTmpImages.php @@ -123,7 +123,7 @@ private function getConfigurations() $result = []; $configurableMatrix = $this->request->getParam('configurable-matrix-serialized', "[]"); if (isset($configurableMatrix) && $configurableMatrix !== "") { - $configurableMatrix = $this->serialize->unserialize($configurableMatrix); + $configurableMatrix = $this->serialize->unserialize($configurableMatrix) ?? []; foreach ($configurableMatrix as $item) { if (empty($item['was_changed']) && empty($item['newProduct'])) { From af5db93fc3fa874be9796291ecb802ceb89141f3 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 22 Aug 2019 17:38:58 +0300 Subject: [PATCH 340/841] MC-18194: Saved Credit Card Feature with Vault is not displaying proper information of card in order information page --- app/code/Magento/Vault/Model/Method/Vault.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index b5d998ee128f6..484f492ba872e 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -543,13 +543,16 @@ private function getPaymentExtensionAttributes(OrderPaymentInterface $payment) */ private function attachCreditCardInfo(OrderPaymentInterface $payment): void { - $paymentToken = $payment->getExtensionAttributes() - ->getVaultPaymentToken(); - if ($paymentToken) { - $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); - if ($tokenDetails && is_array($tokenDetails)) { - $payment->addData($tokenDetails); + try { + $paymentToken = $payment->getExtensionAttributes() + ->getVaultPaymentToken(); + if ($paymentToken) { + $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); + if ($tokenDetails && is_array($tokenDetails)) { + $payment->addData($tokenDetails); + } } + } catch (Exception $e) { } } From 72a9b5df8264f85906d121e9dba57c4566633871 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Thu, 22 Aug 2019 09:46:16 -0500 Subject: [PATCH 341/841] MC-18945: Reading deprecated annotation in schema - added PR build static fixes --- .../Config/GraphQlDeprecatedAnnotationReaderTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php index d41ff92807f85..25187b21a2b8a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php @@ -211,9 +211,9 @@ enumValues(includeDeprecated: true) { if ($field['isDeprecated'] === true) { $typeDeprecatedReason [] = $field['deprecationReason']; } - } - $this->assertNotEmpty($typeDeprecatedReason); - $this->assertContains('Deprecated url_path test', $typeDeprecatedReason); + } + $this->assertNotEmpty($typeDeprecatedReason); + $this->assertContains('Deprecated url_path test', $typeDeprecatedReason); foreach ($enumValuesArray as $enumValue) { if ($enumValue['isDeprecated'] === true) { @@ -221,6 +221,6 @@ enumValues(includeDeprecated: true) { } } $this->assertNotEmpty($enumValueDeprecatedReason); - $this->assertContains('Deprecated SortEnum Value test',$enumValueDeprecatedReason); + $this->assertContains('Deprecated SortEnum Value test', $enumValueDeprecatedReason); } } From 157e1cdea9940c808f558f29af193d0d74bc74ea Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Thu, 22 Aug 2019 22:35:31 +0700 Subject: [PATCH 342/841] "Save Product" with the invalid data must scroll to the error message issue24241 --- app/code/Magento/Ui/view/base/web/js/form/client.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Ui/view/base/web/js/form/client.js b/app/code/Magento/Ui/view/base/web/js/form/client.js index 1c1274512c979..a16c211607e8a 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/client.js +++ b/app/code/Magento/Ui/view/base/web/js/form/client.js @@ -63,6 +63,9 @@ define([ var $wrapper = $('<div/>').addClass(messagesClass).html(msg); $('.page-main-actions', selectorPrefix).after($wrapper); + $('html, body').animate({ + scrollTop: $('.page-main-actions', selectorPrefix).offset().top + }); } }); }); From 71c4b2f887e5a8052b0b78f2ccff58265d425e0c Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Thu, 22 Aug 2019 18:40:03 +0300 Subject: [PATCH 343/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" --- .../Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml index acc7695fb1607..87bb4a72834f4 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml @@ -30,7 +30,7 @@ </actionGroup> <actionGroup name="EnablePayPalSolution" > <annotations> - <description>Expands the 'OTHER PAYPAL PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code.</description> + <description>Expands the 'OTHER PAYPAL PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code without saving.</description> </annotations> <arguments> <argument name="payPalConfigType"/> From b2c8e78eead88e4b1cbd6d4135929f94811dbb21 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 22 Aug 2019 12:00:04 -0500 Subject: [PATCH 344/841] MC-19579: Multiple sort parameters does not work with ElasticSearch --- .../Magento/Framework/Search/Request/Builder.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Search/Request/Builder.php b/lib/internal/Magento/Framework/Search/Request/Builder.php index 74bc65010a934..0cf959b657c76 100644 --- a/lib/internal/Magento/Framework/Search/Request/Builder.php +++ b/lib/internal/Magento/Framework/Search/Request/Builder.php @@ -6,6 +6,7 @@ namespace Magento\Framework\Search\Request; +use Magento\Framework\Api\SortOrder; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Phrase; use Magento\Framework\Search\RequestInterface; @@ -173,7 +174,13 @@ public function create() private function prepareSorts(array $sorts) { $sortData = []; - foreach ($sorts as $sortField => $direction) { + foreach ($sorts as $sortField => $sort) { + if ($sort instanceof SortOrder) { + $sortField = $sort->getField(); + $direction = $sort->getDirection(); + } else { + $direction = $sort; + } $sortData[] = [ 'field' => $sortField, 'direction' => $direction, From 513aaaa2109d3d0a92c0cca46be354a2ec96bff2 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Thu, 22 Aug 2019 12:04:55 -0500 Subject: [PATCH 345/841] magento-engcom/magento2ce#3158: Code style fixes --- .../app/code/Magento/Ui/base/js/lib/validation/rules.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js index 91b07256d0796..0d3aecffe6ae3 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js @@ -69,13 +69,13 @@ define([ expect(rules['validate-number'].handler(value)).toBe(true); }); - + it('Check on space', function () { var value = '10 000'; expect(rules['validate-number'].handler(value)).toBe(true); }); - + it('Check on formatted float (For International price)', function () { var value = '10.000,00'; From 8bba2b7f2d8c4edbdfd684c54174d5abfe64a55f Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 22 Aug 2019 12:19:19 -0500 Subject: [PATCH 346/841] MC-19572: Fix filtering by attribute of type select/multiselect using filter input type "in" --- .../Product/SearchCriteriaBuilder.php | 7 ++++--- .../Model/Config/FilterAttributeReader.php | 1 + .../ProductEntityAttributesForAst.php | 16 +++------------- .../CatalogGraphQl/etc/search_request.xml | 4 ++-- 4 files changed, 10 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index 4872c627ded59..1e646b3c0b74b 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -85,13 +85,14 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte { $searchCriteria = $this->builder->build('products', $args); $this->updateRangeFilters($searchCriteria); - $searchCriteria->setRequestName( - $includeAggregation ? 'graphql_product_search_with_aggregation' : 'graphql_product_search' - ); if ($includeAggregation) { $this->preparePriceAggregation($searchCriteria); + $requestName = 'graphql_product_search_with_aggregation'; + } else { + $requestName = 'graphql_product_search'; } + $searchCriteria->setRequestName($requestName); if (!empty($args['search'])) { $this->addFilter($searchCriteria, 'search_term', $args['search']); diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php index 9310dc593d40c..b2cb4dca28bde 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php @@ -97,6 +97,7 @@ private function getFilterType($attributeType): string 'price' => self::FILTER_RANGE_TYPE, 'date' => self::FILTER_RANGE_TYPE, 'select' => self::FILTER_EQUAL_TYPE, + 'multiselect' => self::FILTER_EQUAL_TYPE, 'boolean' => self::FILTER_EQUAL_TYPE, 'text' => self::FILTER_LIKE_TYPE, 'textarea' => self::FILTER_LIKE_TYPE, diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php index 17409210808cc..973b8fbcd6b0f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/FilterArgument/ProductEntityAttributesForAst.php @@ -29,26 +29,16 @@ class ProductEntityAttributesForAst implements FieldEntityAttributesInterface */ private $additionalAttributes = ['min_price', 'max_price', 'category_id']; - /** - * Array to translate graphql field to internal entity attribute - * - * @var array - */ - private $translatedAttributes = ['category_id' => 'category_ids']; - /** * @param ConfigInterface $config * @param string[] $additionalAttributes - * @param array $translatedAttributes */ public function __construct( ConfigInterface $config, - array $additionalAttributes = [], - array $translatedAttributes = [] + array $additionalAttributes = [] ) { $this->config = $config; $this->additionalAttributes = array_merge($this->additionalAttributes, $additionalAttributes); - $this->translatedAttributes = array_merge($this->translatedAttributes, $translatedAttributes); } /** @@ -74,7 +64,7 @@ public function getEntityAttributes() : array foreach ($configElement->getFields() as $field) { $fields[$field->getName()] = [ 'type' => 'String', - 'fieldName' => $this->translatedAttributes[$field->getName()] ?? $field->getName(), + 'fieldName' => $field->getName(), ]; } } @@ -82,7 +72,7 @@ public function getEntityAttributes() : array foreach ($this->additionalAttributes as $attributeName) { $fields[$attributeName] = [ 'type' => 'String', - 'fieldName' => $this->translatedAttributes[$attributeName] ?? $attributeName, + 'fieldName' => $attributeName, ]; } diff --git a/app/code/Magento/CatalogGraphQl/etc/search_request.xml b/app/code/Magento/CatalogGraphQl/etc/search_request.xml index 5e962d8467a4f..ab1eea9eb6fda 100644 --- a/app/code/Magento/CatalogGraphQl/etc/search_request.xml +++ b/app/code/Magento/CatalogGraphQl/etc/search_request.xml @@ -34,7 +34,7 @@ </query> </queries> <filters> - <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/> + <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_id$"/> <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/> <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> @@ -80,7 +80,7 @@ </query> </queries> <filters> - <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_ids$"/> + <filter xsi:type="termFilter" name="category_filter" field="category_ids" value="$category_id$"/> <filter xsi:type="rangeFilter" name="price_filter" field="price" from="$price.from$" to="$price.to$"/> <filter xsi:type="termFilter" name="visibility_filter" field="visibility" value="$visibility$"/> </filters> From ebb3f2dfb91ee1250549422577c6da023f8693f6 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Thu, 22 Aug 2019 12:42:33 -0500 Subject: [PATCH 347/841] MC-18945: Reading deprecated annotation in schema - added PR build fixes --- .../GraphQlDeprecatedAnnotationReaderTest.php | 226 ------------------ .../GraphQl/Config/GraphQlReaderTest.php | 160 +++++++++++++ 2 files changed, 160 insertions(+), 226 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php deleted file mode 100644 index 25187b21a2b8a..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlDeprecatedAnnotationReaderTest.php +++ /dev/null @@ -1,226 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQl\Config; - -use Magento\Framework\App\Cache; -use Magento\Framework\App\Request\Http; -use Magento\Framework\GraphQl\Config; -use Magento\Framework\GraphQl\Schema\SchemaGenerator; -use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\Serialize\SerializerInterface; -use Magento\GraphQl\Controller\GraphQl; - -/** - * Tests the entire process of generating a schema from a given SDL and processing a request/query - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * @magentoAppArea graphql - */ -class GraphQlDeprecatedAnnotationReaderTest extends \PHPUnit\Framework\TestCase -{ - /** @var Config */ - private $configModel; - - /** @var GraphQl */ - private $graphQlController; - - /** @var ObjectManagerInterface */ - private $objectManager; - - /** @var SerializerInterface */ - private $jsonSerializer; - - /** - * @inheritdoc - */ - protected function setUp() - { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var Cache $cache */ - $cache = $this->objectManager->get(Cache::class); - $cache->clean(); - $fileResolverMock = $this->getMockBuilder( - \Magento\Framework\Config\FileResolverInterface::class - )->disableOriginalConstructor()->getMock(); - $fileList = [ - file_get_contents(__DIR__ . '/../_files/schemaE.graphqls') - ]; - $fileResolverMock->expects($this->any())->method('get')->will($this->returnValue($fileList)); - $graphQlReader = $this->objectManager->create( - \Magento\Framework\GraphQlSchemaStitching\GraphQlReader::class, - ['fileResolver' => $fileResolverMock] - ); - $reader = $this->objectManager->create( - \Magento\Framework\GraphQlSchemaStitching\Reader::class, - ['readers' => ['graphql_reader' => $graphQlReader]] - ); - $data = $this->objectManager->create( - \Magento\Framework\GraphQl\Config\Data ::class, - ['reader' => $reader] - ); - $this->configModel = $this->objectManager->create( - \Magento\Framework\GraphQl\Config::class, - ['data' => $data] - ); - $outputMapper = $this->objectManager->create( - \Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper::class, - ['config' => $this->configModel] - ); - $schemaGenerator = $this->objectManager->create( - SchemaGenerator::class, - ['outputMapper' => $outputMapper] - ); - $this->graphQlController = $this->objectManager->create( - GraphQl::class, - ['schemaGenerator' => $schemaGenerator] - ); - $this->jsonSerializer = $this->objectManager->get(SerializerInterface::class); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testDispatchIntrospectionWithCustomSDL() - { - $query - = <<<QUERY - query IntrospectionQuery { - __schema { - queryType { name } - types { - ...FullType - } - } -} - -fragment FullType on __Type { - kind - name - description - fields(includeDeprecated: true) { - name - description - args { - ...InputValue - } - type { - ...TypeRef - } - isDeprecated - deprecationReason - } - inputFields { - ...InputValue - } - interfaces { - ...TypeRef - } - enumValues(includeDeprecated: true) { - name - description - isDeprecated - deprecationReason - } - possibleTypes { - ...TypeRef - } -} - -fragment InputValue on __InputValue { - name - description - type { ...TypeRef } - defaultValue -} - -fragment TypeRef on __Type { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - } - } - } - } - } - } - } -} - - -QUERY; - $postData = [ - 'query' => $query, - 'variables' => null, - 'operationName' => 'IntrospectionQuery' - ]; - /** @var Http $request */ - $request = $this->objectManager->get(Http::class); - $request->setPathInfo('/graphql'); - $request->setMethod('POST'); - $request->setContent(json_encode($postData)); - $headers = $this->objectManager->create(\Zend\Http\Headers::class) - ->addHeaders(['Content-Type' => 'application/json']); - $request->setHeaders($headers); - - $response = $this->graphQlController->dispatch($request); - $this->jsonSerializer->unserialize($response->getContent()); - $expectedOutput = require __DIR__ . '/../_files/schema_response_sdl_deprecated_annotation.php'; - - //Checks to make sure that the given description exists in the expectedOutput array - - $this->assertTrue( - array_key_exists( - array_search( - 'Comment for SortEnum', - array_column($expectedOutput, 'description') - ), - $expectedOutput - ) - ); - - //Checks to make sure that the given deprecatedReason exists in the expectedOutput array for enumValues, fields. - $fieldsArray = $expectedOutput[0]['fields']; - $enumValuesArray = $expectedOutput[1]['enumValues']; - - foreach ($fieldsArray as $field) { - if ($field['isDeprecated'] === true) { - $typeDeprecatedReason [] = $field['deprecationReason']; - } - } - $this->assertNotEmpty($typeDeprecatedReason); - $this->assertContains('Deprecated url_path test', $typeDeprecatedReason); - - foreach ($enumValuesArray as $enumValue) { - if ($enumValue['isDeprecated'] === true) { - $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; - } - } - $this->assertNotEmpty($enumValueDeprecatedReason); - $this->assertContains('Deprecated SortEnum Value test', $enumValueDeprecatedReason); - } -} diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index af8d1c7af134b..a75935d2ec157 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -240,5 +240,165 @@ enumValues(includeDeprecated: true) { $expectedOutput ) ); + + } + + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testDispatchIntrospectionWithDeprecatedSDL() + { + + + + $query + = <<<QUERY + query IntrospectionQuery { + __schema { + queryType { name } + types { + ...FullType + } + } +} + +fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } +} + +fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue +} + +fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } +} + + +QUERY; + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var Cache $cache */ + $cache = $this->objectManager->get(Cache::class); + $cache->clean(); + $fileResolverMock = $this->getMockBuilder( + \Magento\Framework\Config\FileResolverInterface::class + )->disableOriginalConstructor()->getMock(); + $fileList = [ + file_get_contents(__DIR__ . '/../_files/schemaE.graphqls') + ]; + $fileResolverMock->expects($this->any())->method('get')->will($this->returnValue($fileList)); + + $postData = [ + 'query' => $query, + 'variables' => null, + 'operationName' => 'IntrospectionQuery' + ]; + /** @var Http $request */ + $request = $this->objectManager->get(Http::class); + $request->setPathInfo('/graphql'); + $request->setMethod('POST'); + $request->setContent(json_encode($postData)); + $headers = $this->objectManager->create(\Zend\Http\Headers::class) + ->addHeaders(['Content-Type' => 'application/json']); + $request->setHeaders($headers); + + $response = $this->graphQlController->dispatch($request); + $this->jsonSerializer->unserialize($response->getContent()); + $expectedOutput = require __DIR__ . '/../_files/schema_response_sdl_deprecated_annotation.php'; + + + //Checks to make sure that the given description exists in the expectedOutput array + + $this->assertTrue( + array_key_exists( + array_search( + 'Comment for SortEnum', + array_column($expectedOutput, 'description') + ), + $expectedOutput + ) + ); + + //Checks to make sure that the given deprecatedReason exists in the expectedOutput array for enumValues, fields. + $fieldsArray = $expectedOutput[0]['fields']; + $enumValuesArray = $expectedOutput[1]['enumValues']; + + foreach ($fieldsArray as $field) { + if ($field['isDeprecated'] === true) { + $typeDeprecatedReason [] = $field['deprecationReason']; + } + } + $this->assertNotEmpty($typeDeprecatedReason); + $this->assertContains('Deprecated url_path test', $typeDeprecatedReason); + + foreach ($enumValuesArray as $enumValue) { + if ($enumValue['isDeprecated'] === true) { + $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; + } + } + $this->assertNotEmpty($enumValueDeprecatedReason); + $this->assertContains('Deprecated SortEnum Value test', $enumValueDeprecatedReason); } } From c668430a4989ca5177aa5bffa774d3b07584d48d Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Thu, 22 Aug 2019 12:47:06 -0500 Subject: [PATCH 348/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../Helper/File/Storage/DatabaseTest.php | 2 +- .../Magento/MediaStorage/_files/database_mode.php | 1 + .../_files/database_mode_rollback.php | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php b/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php index 92a3ee4181122..8f3feaf5c24f4 100644 --- a/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php +++ b/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php @@ -50,7 +50,7 @@ protected function setUp() /** * test for \Magento\MediaStorage\Model\File\Storage\Database::deleteFolder() * - * @magentoDataFixture Magento/MediaStorage/_files/database_mode.php + * @magentoDataFixtureBeforeTransaction Magento/MediaStorage/_files/database_mode.php */ public function testDeleteFolder() { diff --git a/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php index 1330fe8ea6af7..ef92d6668be4a 100644 --- a/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php +++ b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php @@ -12,6 +12,7 @@ $database = $objectManager->get(\Magento\MediaStorage\Helper\File\Storage\Database::class); $database->getStorageDatabaseModel()->init(); +/** @var Magento\Framework\App\Config\ConfigResource\ConfigInterface $config */ $config = $objectManager->get(Magento\Framework\App\Config\ConfigResource\ConfigInterface::class); $config->saveConfig('system/media_storage_configuration/media_storage', '1'); $config->saveConfig('system/media_storage_configuration/media_database', 'default_setup'); diff --git a/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode_rollback.php b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode_rollback.php new file mode 100644 index 0000000000000..b226a8de54976 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode_rollback.php @@ -0,0 +1,15 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var Magento\Framework\App\Config\ConfigResource\ConfigInterface $config */ +$config = $objectManager->get(Magento\Framework\App\Config\ConfigResource\ConfigInterface::class); +$config->deleteConfig('system/media_storage_configuration/media_storage'); +$config->deleteConfig('system/media_storage_configuration/media_database'); +$objectManager->get(Magento\Framework\App\Config\ReinitableConfigInterface::class)->reinit(); From dce0a2f9a844b038143cbd5b554184e9987bae4c Mon Sep 17 00:00:00 2001 From: Nicolas Medina <nicolas@magemontreal.com> Date: Thu, 22 Aug 2019 14:12:08 -0400 Subject: [PATCH 349/841] Update access_denied.phtml Removed unnecessary translations (URL and javascript code) --- .../view/adminhtml/templates/admin/access_denied.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml index be309423c48d2..3cc2594a75801 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/access_denied.phtml @@ -21,10 +21,10 @@ <li> <span><?= $block->escapeHtml(__('Return to ')) ?> <?php if (isset($_SERVER['HTTP_REFERER'])) : ?> - <a href="<?= $block->escapeUrl(__($_SERVER['HTTP_REFERER'])) ?>"> + <a href="<?= $block->escapeUrl($_SERVER['HTTP_REFERER']) ?>"> <?= $block->escapeHtml(__('previous page')) ?></a><?= $block->escapeHtml(__('.')) ?> <?php else : ?> - <a href="<?= $block->escapeHtmlAttr(__('javascript:history.back()')) ?>"> + <a href="<?= $block->escapeHtmlAttr('javascript:history.back()') ?>"> <?= $block->escapeHtml(__('previous page')) ?></a><?= $block->escapeHtml(__('.')) ?> <?php endif ?> </span> From 037a5b5f334f4e46fc7202a05e8761b5936dd97a Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Thu, 22 Aug 2019 14:50:59 -0500 Subject: [PATCH 350/841] MC-18945: Reading deprecated annotation in schema - added the changes form review --- .../DownloadableGraphQl/etc/schema.graphqls | 4 ++-- .../GraphQlReader/Reader/EnumType.php | 19 ++----------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index 2b986694e2996..8fbce74741895 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -53,8 +53,8 @@ type DownloadableProduct implements ProductInterface, CustomizableProductInterfa links_title: String @doc(description: "The heading above the list of downloadable products") } -enum DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample") { - FILE +enum DownloadableFileTypeEnum { + FILE @deprecated(reason: "`sample_url` serves to get the downloadable sample") URL } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php index a6a629d581b4a..c05d58f2b4b03 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php @@ -9,7 +9,7 @@ use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; -use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DeprecatedEnumAnnotationReader; + /** * Composite configuration reader to handle the enum type meta @@ -21,21 +21,13 @@ class EnumType implements TypeMetaReaderInterface */ private $docReader; - /** - * @var DeprecatedEnumAnnotationReader - */ - private $deprecatedEnumAnnotationReader; - /** * @param DocReader $docReader - * @param DeprecatedEnumAnnotationReader $deprecatedEnumAnnotationReader */ public function __construct( - DocReader $docReader, - DeprecatedEnumAnnotationReader $deprecatedEnumAnnotationReader + DocReader $docReader ) { $this->docReader = $docReader; - $this->deprecatedEnumAnnotationReader = $deprecatedEnumAnnotationReader; } /** @@ -61,13 +53,6 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $result['items'][$enumValueMeta->value]['description'] = $this->docReader->read($enumValueMeta->astNode->directives); } - - if (!empty($enumValueMeta->deprecationReason) && - $this->deprecatedEnumAnnotationReader->read($enumValueMeta->astNode->directives) - ) { - $result['items'][$enumValueMeta->value]['deprecationReason'] = - $this->deprecatedEnumAnnotationReader->read($enumValueMeta->astNode->directives); - } } if ($this->docReader->read($typeMeta->astNode->directives)) { From 41853998dd79439a47f8610da947672577395c12 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Thu, 22 Aug 2019 14:55:01 -0500 Subject: [PATCH 351/841] MC-18945: Reading deprecated annotation in schema - added the changes form review --- .../GraphQl/Config/Element/EnumValue.php | 2 +- .../DeprecatedEnumAnnotationReader.php | 34 ------------------- .../GraphQlReader/Reader/EnumType.php | 1 - 3 files changed, 1 insertion(+), 36 deletions(-) delete mode 100644 lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php index b9ddc79e7fa4c..22bb1db8d2787 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php @@ -79,7 +79,7 @@ public function getDescription() : string } /** - * Get the enum value's description. + * Get the enum value's deprecatedReason. * * @return string */ diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php deleted file mode 100644 index 1c9795ec77cbd..0000000000000 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedEnumAnnotationReader.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader; - -/** - * Reads documentation from the annotation "@deprecated" of an AST node - */ -class DeprecatedEnumAnnotationReader -{ - /** - * Read deprecated annotation for a specific node if exists - * - * @param \GraphQL\Language\AST\NodeList $directives - * @return array - */ - public function read(\GraphQL\Language\AST\NodeList $directives) : string - { - foreach ($directives as $directive) { - if ($directive->name->value == 'deprecated') { - foreach ($directive->arguments as $directiveArgument) { - if ($directiveArgument->name->value == 'deprecationReason') { - return $directiveArgument->value->value; - } - } - } - } - return ''; - } -} diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php index c05d58f2b4b03..e4dec7afdab0a 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php @@ -10,7 +10,6 @@ use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; - /** * Composite configuration reader to handle the enum type meta */ From b20e4c9086ab366fef91407398b95d8be524a6d1 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Thu, 22 Aug 2019 14:59:05 -0500 Subject: [PATCH 352/841] MC-18945: Reading deprecated annotation in schema - added the schema changes --- app/code/Magento/DownloadableGraphQl/etc/schema.graphqls | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index 8fbce74741895..0d1d392592219 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -53,9 +53,9 @@ type DownloadableProduct implements ProductInterface, CustomizableProductInterfa links_title: String @doc(description: "The heading above the list of downloadable products") } -enum DownloadableFileTypeEnum { +enum DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample") { FILE @deprecated(reason: "`sample_url` serves to get the downloadable sample") - URL + URL @deprecated(reason: "`sample_url` serves to get the downloadable sample") } type DownloadableProductLinks @doc(description: "DownloadableProductLinks defines characteristics of a downloadable product") { From 976188b7f74d6bd7f3bec51629e8f8a1a37fecda Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Thu, 22 Aug 2019 14:59:26 -0500 Subject: [PATCH 353/841] magento-engcom/magento2ce#3158: Code style fixes --- .../app/code/Magento/Ui/base/js/lib/validation/rules.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js index 0d3aecffe6ae3..d60d9ad9a5b0f 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js @@ -83,7 +83,7 @@ define([ }); it('Check on formatted float (For International price)', function () { - var value = "10'000.00"; + var value = '10\'000.00'; expect(rules['validate-number'].handler(value)).toBe(true); }); From c560c661189b1e37a24b7cb79f07de9727ded804 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Thu, 22 Aug 2019 23:14:22 +0300 Subject: [PATCH 354/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" --- .../Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml | 2 +- .../Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml index 87bb4a72834f4..c3e99854ce3ac 100644 --- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml @@ -28,7 +28,7 @@ <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForPageLoad2"/> </actionGroup> - <actionGroup name="EnablePayPalSolution" > + <actionGroup name="EnablePayPalSolutionWithoutSave" > <annotations> <description>Expands the 'OTHER PAYPAL PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code without saving.</description> </annotations> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml index e1ab68f6de8e9..c653c1f03fd74 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml @@ -39,13 +39,13 @@ <!--Try to enable express checkout Solution--> <comment userInput="Try to enable express checkout Solution" stepKey="commentTryEnableExpressCheckout"/> <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> - <actionGroup ref="EnablePayPalSolution" stepKey="enableExpressCheckout"> + <actionGroup ref="EnablePayPalSolutionWithoutSave" stepKey="enableExpressCheckout"> <argument name="payPalConfigType" value="WPSExpressConfigSection"/> <argument name="countryCode" value="us"/> </actionGroup> <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> <waitForPageLoad stepKey="waitForPageLoad2"/> - <actionGroup ref="EnablePayPalSolution" stepKey="enableExpressCheckout2"> + <actionGroup ref="EnablePayPalSolutionWithoutSave" stepKey="enableExpressCheckout2"> <argument name="payPalConfigType" value="PayPalExpressCheckoutConfigSection"/> <argument name="countryCode" value="us"/> </actionGroup> From 8bfc3448edd31e675e1215740655e2c92d039f94 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Thu, 22 Aug 2019 15:18:56 -0500 Subject: [PATCH 355/841] MC-18945: Reading deprecated annotation in schema - added the changes from static and integration fixes --- .../Framework/GraphQl/Config/GraphQlReaderTest.php | 12 ++++-------- .../GraphQlReader/MetaReader/FieldMetaReader.php | 3 ++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index a75935d2ec157..6b7404b398501 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -200,11 +200,13 @@ enumValues(includeDeprecated: true) { $sortFields = ['inputFields', 'fields']; foreach ($sortFields as $sortField) { isset($searchTerm[$sortField]) && is_array($searchTerm[$sortField]) - ? usort($searchTerm[$sortField], function ($a, $b) { + ? usort( + $searchTerm[$sortField], function ($a, $b) { $cmpField = 'name'; return isset($a[$cmpField]) && isset($b[$cmpField]) ? strcmp($a[$cmpField], $b[$cmpField]) : 0; - }) : null; + } + ) : null; } $this->assertTrue( @@ -240,18 +242,13 @@ enumValues(includeDeprecated: true) { $expectedOutput ) ); - } - /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testDispatchIntrospectionWithDeprecatedSDL() { - - - $query = <<<QUERY query IntrospectionQuery { @@ -368,7 +365,6 @@ enumValues(includeDeprecated: true) { $this->jsonSerializer->unserialize($response->getContent()); $expectedOutput = require __DIR__ . '/../_files/schema_response_sdl_deprecated_annotation.php'; - //Checks to make sure that the given description exists in the expectedOutput array $this->assertTrue( diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index d2374e92ce255..c92dcf39460a8 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -100,7 +100,8 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra } $typeMeta = $argumentMeta->getType(); - $result['arguments'][$argumentName] = array_merge_recursive( + // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge + $result['arguments'][$argumentName] = array_merge( $result['arguments'][$argumentName], $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) ); From 31f37d04576e54ed6e26be6da3ce79a322d1ec89 Mon Sep 17 00:00:00 2001 From: Dmytro Poperechnyy <dpoperechnyy@magento.com> Date: Thu, 22 Aug 2019 16:56:33 -0500 Subject: [PATCH 356/841] MC-19245: jQuery cookie shim missing --- app/code/Magento/Theme/view/base/requirejs-config.js | 7 ------- lib/web/jquery/jquery.storageapi.min.js | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/code/Magento/Theme/view/base/requirejs-config.js b/app/code/Magento/Theme/view/base/requirejs-config.js index 2822d6db008a1..befaa5b7eb0fe 100644 --- a/app/code/Magento/Theme/view/base/requirejs-config.js +++ b/app/code/Magento/Theme/view/base/requirejs-config.js @@ -19,20 +19,13 @@ var config = { 'jquery/hover-intent': ['jquery'], 'mage/adminhtml/backup': ['prototype'], 'mage/captcha': ['prototype'], - 'mage/common': ['jquery'], 'mage/new-gallery': ['jquery'], 'mage/webapi': ['jquery'], 'jquery/ui': ['jquery'], 'MutationObserver': ['es6-collections'], - 'moment': { - 'exports': 'moment' - }, 'matchMedia': { 'exports': 'mediaCheck' }, - 'jquery/jquery-storageapi': { - 'deps': ['jquery/jquery.cookie'] - } }, 'paths': { 'jquery/validate': 'jquery/jquery.validate', diff --git a/lib/web/jquery/jquery.storageapi.min.js b/lib/web/jquery/jquery.storageapi.min.js index e196b0678934b..886c3d847ed3b 100644 --- a/lib/web/jquery/jquery.storageapi.min.js +++ b/lib/web/jquery/jquery.storageapi.min.js @@ -1,2 +1,2 @@ /* jQuery Storage API Plugin 1.7.3 https://github.com/julien-maurel/jQuery-Storage-API */ -!function(e){"function"==typeof define&&define.amd?define(["jquery"],e):e("object"==typeof exports?require("jquery"):jQuery)}(function(e){function t(t){var r,i,n,o=arguments.length,s=window[t],a=arguments,u=a[1];if(2>o)throw Error("Minimum 2 arguments must be given");if(e.isArray(u)){i={};for(var f in u){r=u[f];try{i[r]=JSON.parse(s.getItem(r))}catch(c){i[r]=s.getItem(r)}}return i}if(2!=o){try{i=JSON.parse(s.getItem(u))}catch(c){throw new ReferenceError(u+" is not defined in this storage")}for(var f=2;o-1>f;f++)if(i=i[a[f]],void 0===i)throw new ReferenceError([].slice.call(a,1,f+1).join(".")+" is not defined in this storage");if(e.isArray(a[f])){n=i,i={};for(var m in a[f])i[a[f][m]]=n[a[f][m]];return i}return i[a[f]]}try{return JSON.parse(s.getItem(u))}catch(c){return s.getItem(u)}}function r(t){var r,i,n=arguments.length,o=window[t],s=arguments,a=s[1],u=s[2],f={};if(2>n||!e.isPlainObject(a)&&3>n)throw Error("Minimum 3 arguments must be given or second parameter must be an object");if(e.isPlainObject(a)){for(var c in a)r=a[c],e.isPlainObject(r)?o.setItem(c,JSON.stringify(r)):o.setItem(c,r);return a}if(3==n)return"object"==typeof u?o.setItem(a,JSON.stringify(u)):o.setItem(a,u),u;try{i=o.getItem(a),null!=i&&(f=JSON.parse(i))}catch(m){}i=f;for(var c=2;n-2>c;c++)r=s[c],i[r]&&e.isPlainObject(i[r])||(i[r]={}),i=i[r];return i[s[c]]=s[c+1],o.setItem(a,JSON.stringify(f)),f}function i(t){var r,i,n=arguments.length,o=window[t],s=arguments,a=s[1];if(2>n)throw Error("Minimum 2 arguments must be given");if(e.isArray(a)){for(var u in a)o.removeItem(a[u]);return!0}if(2==n)return o.removeItem(a),!0;try{r=i=JSON.parse(o.getItem(a))}catch(f){throw new ReferenceError(a+" is not defined in this storage")}for(var u=2;n-1>u;u++)if(i=i[s[u]],void 0===i)throw new ReferenceError([].slice.call(s,1,u).join(".")+" is not defined in this storage");if(e.isArray(s[u]))for(var c in s[u])delete i[s[u][c]];else delete i[s[u]];return o.setItem(a,JSON.stringify(r)),!0}function n(t,r){var n=a(t);for(var o in n)i(t,n[o]);if(r)for(var o in e.namespaceStorages)u(o)}function o(r){var i=arguments.length,n=arguments,s=(window[r],n[1]);if(1==i)return 0==a(r).length;if(e.isArray(s)){for(var u=0;u<s.length;u++)if(!o(r,s[u]))return!1;return!0}try{var f=t.apply(this,arguments);e.isArray(n[i-1])||(f={totest:f});for(var u in f)if(!(e.isPlainObject(f[u])&&e.isEmptyObject(f[u])||e.isArray(f[u])&&!f[u].length)&&f[u])return!1;return!0}catch(c){return!0}}function s(r){var i=arguments.length,n=arguments,o=(window[r],n[1]);if(2>i)throw Error("Minimum 2 arguments must be given");if(e.isArray(o)){for(var a=0;a<o.length;a++)if(!s(r,o[a]))return!1;return!0}try{var u=t.apply(this,arguments);e.isArray(n[i-1])||(u={totest:u});for(var a in u)if(void 0===u[a]||null===u[a])return!1;return!0}catch(f){return!1}}function a(r){var i=arguments.length,n=window[r],o=arguments,s=(o[1],[]),a={};if(a=i>1?t.apply(this,o):n,a._cookie)for(var u in e.cookie())""!=u&&s.push(u.replace(a._prefix,""));else for(var f in a)s.push(f);return s}function u(t){if(!t||"string"!=typeof t)throw Error("First parameter must be a string");g?(window.localStorage.getItem(t)||window.localStorage.setItem(t,"{}"),window.sessionStorage.getItem(t)||window.sessionStorage.setItem(t,"{}")):(window.localCookieStorage.getItem(t)||window.localCookieStorage.setItem(t,"{}"),window.sessionCookieStorage.getItem(t)||window.sessionCookieStorage.setItem(t,"{}"));var r={localStorage:e.extend({},e.localStorage,{_ns:t}),sessionStorage:e.extend({},e.sessionStorage,{_ns:t})};return e.cookie&&(window.cookieStorage.getItem(t)||window.cookieStorage.setItem(t,"{}"),r.cookieStorage=e.extend({},e.cookieStorage,{_ns:t})),e.namespaceStorages[t]=r,r}function f(e){if(!window[e])return!1;var t="jsapi";try{return window[e].setItem(t,t),window[e].removeItem(t),!0}catch(r){return!1}}var c="ls_",m="ss_",g=f("localStorage"),h={_type:"",_ns:"",_callMethod:function(e,t){var r=[this._type],t=Array.prototype.slice.call(t),i=t[0];return this._ns&&r.push(this._ns),"string"==typeof i&&-1!==i.indexOf(".")&&(t.shift(),[].unshift.apply(t,i.split("."))),[].push.apply(r,t),e.apply(this,r)},get:function(){return this._callMethod(t,arguments)},set:function(){var t=arguments.length,i=arguments,n=i[0];if(1>t||!e.isPlainObject(n)&&2>t)throw Error("Minimum 2 arguments must be given or first parameter must be an object");if(e.isPlainObject(n)&&this._ns){for(var o in n)r(this._type,this._ns,o,n[o]);return n}var s=this._callMethod(r,i);return this._ns?s[n.split(".")[0]]:s},remove:function(){if(arguments.length<1)throw Error("Minimum 1 argument must be given");return this._callMethod(i,arguments)},removeAll:function(e){return this._ns?(r(this._type,this._ns,{}),!0):n(this._type,e)},isEmpty:function(){return this._callMethod(o,arguments)},isSet:function(){if(arguments.length<1)throw Error("Minimum 1 argument must be given");return this._callMethod(s,arguments)},keys:function(){return this._callMethod(a,arguments)}};if(e.cookie){window.name||(window.name=Math.floor(1e8*Math.random()));var l={_cookie:!0,_prefix:"",_expires:null,_path:null,_domain:null,setItem:function(t,r){e.cookie(this._prefix+t,r,{expires:this._expires,path:this._path,domain:this._domain})},getItem:function(t){return e.cookie(this._prefix+t)},removeItem:function(t){return e.removeCookie(this._prefix+t)},clear:function(){for(var t in e.cookie())""!=t&&(!this._prefix&&-1===t.indexOf(c)&&-1===t.indexOf(m)||this._prefix&&0===t.indexOf(this._prefix))&&e.removeCookie(t)},setExpires:function(e){return this._expires=e,this},setPath:function(e){return this._path=e,this},setDomain:function(e){return this._domain=e,this},setConf:function(e){return e.path&&(this._path=e.path),e.domain&&(this._domain=e.domain),e.expires&&(this._expires=e.expires),this},setDefaultConf:function(){this._path=this._domain=this._expires=null}};g||(window.localCookieStorage=e.extend({},l,{_prefix:c,_expires:3650}),window.sessionCookieStorage=e.extend({},l,{_prefix:m+window.name+"_"})),window.cookieStorage=e.extend({},l),e.cookieStorage=e.extend({},h,{_type:"cookieStorage",setExpires:function(e){return window.cookieStorage.setExpires(e),this},setPath:function(e){return window.cookieStorage.setPath(e),this},setDomain:function(e){return window.cookieStorage.setDomain(e),this},setConf:function(e){return window.cookieStorage.setConf(e),this},setDefaultConf:function(){return window.cookieStorage.setDefaultConf(),this}})}e.initNamespaceStorage=function(e){return u(e)},g?(e.localStorage=e.extend({},h,{_type:"localStorage"}),e.sessionStorage=e.extend({},h,{_type:"sessionStorage"})):(e.localStorage=e.extend({},h,{_type:"localCookieStorage"}),e.sessionStorage=e.extend({},h,{_type:"sessionCookieStorage"})),e.namespaceStorages={},e.removeAllStorages=function(t){e.localStorage.removeAll(t),e.sessionStorage.removeAll(t),e.cookieStorage&&e.cookieStorage.removeAll(t),t||(e.namespaceStorages={})}}); \ No newline at end of file +!function(e){"function"==typeof define&&define.amd?define(["jquery", "jquery/jquery.cookie"],e):e("object"==typeof exports?require("jquery"):jQuery)}(function(e){function t(t){var r,i,n,o=arguments.length,s=window[t],a=arguments,u=a[1];if(2>o)throw Error("Minimum 2 arguments must be given");if(e.isArray(u)){i={};for(var f in u){r=u[f];try{i[r]=JSON.parse(s.getItem(r))}catch(c){i[r]=s.getItem(r)}}return i}if(2!=o){try{i=JSON.parse(s.getItem(u))}catch(c){throw new ReferenceError(u+" is not defined in this storage")}for(var f=2;o-1>f;f++)if(i=i[a[f]],void 0===i)throw new ReferenceError([].slice.call(a,1,f+1).join(".")+" is not defined in this storage");if(e.isArray(a[f])){n=i,i={};for(var m in a[f])i[a[f][m]]=n[a[f][m]];return i}return i[a[f]]}try{return JSON.parse(s.getItem(u))}catch(c){return s.getItem(u)}}function r(t){var r,i,n=arguments.length,o=window[t],s=arguments,a=s[1],u=s[2],f={};if(2>n||!e.isPlainObject(a)&&3>n)throw Error("Minimum 3 arguments must be given or second parameter must be an object");if(e.isPlainObject(a)){for(var c in a)r=a[c],e.isPlainObject(r)?o.setItem(c,JSON.stringify(r)):o.setItem(c,r);return a}if(3==n)return"object"==typeof u?o.setItem(a,JSON.stringify(u)):o.setItem(a,u),u;try{i=o.getItem(a),null!=i&&(f=JSON.parse(i))}catch(m){}i=f;for(var c=2;n-2>c;c++)r=s[c],i[r]&&e.isPlainObject(i[r])||(i[r]={}),i=i[r];return i[s[c]]=s[c+1],o.setItem(a,JSON.stringify(f)),f}function i(t){var r,i,n=arguments.length,o=window[t],s=arguments,a=s[1];if(2>n)throw Error("Minimum 2 arguments must be given");if(e.isArray(a)){for(var u in a)o.removeItem(a[u]);return!0}if(2==n)return o.removeItem(a),!0;try{r=i=JSON.parse(o.getItem(a))}catch(f){throw new ReferenceError(a+" is not defined in this storage")}for(var u=2;n-1>u;u++)if(i=i[s[u]],void 0===i)throw new ReferenceError([].slice.call(s,1,u).join(".")+" is not defined in this storage");if(e.isArray(s[u]))for(var c in s[u])delete i[s[u][c]];else delete i[s[u]];return o.setItem(a,JSON.stringify(r)),!0}function n(t,r){var n=a(t);for(var o in n)i(t,n[o]);if(r)for(var o in e.namespaceStorages)u(o)}function o(r){var i=arguments.length,n=arguments,s=(window[r],n[1]);if(1==i)return 0==a(r).length;if(e.isArray(s)){for(var u=0;u<s.length;u++)if(!o(r,s[u]))return!1;return!0}try{var f=t.apply(this,arguments);e.isArray(n[i-1])||(f={totest:f});for(var u in f)if(!(e.isPlainObject(f[u])&&e.isEmptyObject(f[u])||e.isArray(f[u])&&!f[u].length)&&f[u])return!1;return!0}catch(c){return!0}}function s(r){var i=arguments.length,n=arguments,o=(window[r],n[1]);if(2>i)throw Error("Minimum 2 arguments must be given");if(e.isArray(o)){for(var a=0;a<o.length;a++)if(!s(r,o[a]))return!1;return!0}try{var u=t.apply(this,arguments);e.isArray(n[i-1])||(u={totest:u});for(var a in u)if(void 0===u[a]||null===u[a])return!1;return!0}catch(f){return!1}}function a(r){var i=arguments.length,n=window[r],o=arguments,s=(o[1],[]),a={};if(a=i>1?t.apply(this,o):n,a._cookie)for(var u in e.cookie())""!=u&&s.push(u.replace(a._prefix,""));else for(var f in a)s.push(f);return s}function u(t){if(!t||"string"!=typeof t)throw Error("First parameter must be a string");g?(window.localStorage.getItem(t)||window.localStorage.setItem(t,"{}"),window.sessionStorage.getItem(t)||window.sessionStorage.setItem(t,"{}")):(window.localCookieStorage.getItem(t)||window.localCookieStorage.setItem(t,"{}"),window.sessionCookieStorage.getItem(t)||window.sessionCookieStorage.setItem(t,"{}"));var r={localStorage:e.extend({},e.localStorage,{_ns:t}),sessionStorage:e.extend({},e.sessionStorage,{_ns:t})};return e.cookie&&(window.cookieStorage.getItem(t)||window.cookieStorage.setItem(t,"{}"),r.cookieStorage=e.extend({},e.cookieStorage,{_ns:t})),e.namespaceStorages[t]=r,r}function f(e){if(!window[e])return!1;var t="jsapi";try{return window[e].setItem(t,t),window[e].removeItem(t),!0}catch(r){return!1}}var c="ls_",m="ss_",g=f("localStorage"),h={_type:"",_ns:"",_callMethod:function(e,t){var r=[this._type],t=Array.prototype.slice.call(t),i=t[0];return this._ns&&r.push(this._ns),"string"==typeof i&&-1!==i.indexOf(".")&&(t.shift(),[].unshift.apply(t,i.split("."))),[].push.apply(r,t),e.apply(this,r)},get:function(){return this._callMethod(t,arguments)},set:function(){var t=arguments.length,i=arguments,n=i[0];if(1>t||!e.isPlainObject(n)&&2>t)throw Error("Minimum 2 arguments must be given or first parameter must be an object");if(e.isPlainObject(n)&&this._ns){for(var o in n)r(this._type,this._ns,o,n[o]);return n}var s=this._callMethod(r,i);return this._ns?s[n.split(".")[0]]:s},remove:function(){if(arguments.length<1)throw Error("Minimum 1 argument must be given");return this._callMethod(i,arguments)},removeAll:function(e){return this._ns?(r(this._type,this._ns,{}),!0):n(this._type,e)},isEmpty:function(){return this._callMethod(o,arguments)},isSet:function(){if(arguments.length<1)throw Error("Minimum 1 argument must be given");return this._callMethod(s,arguments)},keys:function(){return this._callMethod(a,arguments)}};if(e.cookie){window.name||(window.name=Math.floor(1e8*Math.random()));var l={_cookie:!0,_prefix:"",_expires:null,_path:null,_domain:null,setItem:function(t,r){e.cookie(this._prefix+t,r,{expires:this._expires,path:this._path,domain:this._domain})},getItem:function(t){return e.cookie(this._prefix+t)},removeItem:function(t){return e.removeCookie(this._prefix+t)},clear:function(){for(var t in e.cookie())""!=t&&(!this._prefix&&-1===t.indexOf(c)&&-1===t.indexOf(m)||this._prefix&&0===t.indexOf(this._prefix))&&e.removeCookie(t)},setExpires:function(e){return this._expires=e,this},setPath:function(e){return this._path=e,this},setDomain:function(e){return this._domain=e,this},setConf:function(e){return e.path&&(this._path=e.path),e.domain&&(this._domain=e.domain),e.expires&&(this._expires=e.expires),this},setDefaultConf:function(){this._path=this._domain=this._expires=null}};g||(window.localCookieStorage=e.extend({},l,{_prefix:c,_expires:3650}),window.sessionCookieStorage=e.extend({},l,{_prefix:m+window.name+"_"})),window.cookieStorage=e.extend({},l),e.cookieStorage=e.extend({},h,{_type:"cookieStorage",setExpires:function(e){return window.cookieStorage.setExpires(e),this},setPath:function(e){return window.cookieStorage.setPath(e),this},setDomain:function(e){return window.cookieStorage.setDomain(e),this},setConf:function(e){return window.cookieStorage.setConf(e),this},setDefaultConf:function(){return window.cookieStorage.setDefaultConf(),this}})}e.initNamespaceStorage=function(e){return u(e)},g?(e.localStorage=e.extend({},h,{_type:"localStorage"}),e.sessionStorage=e.extend({},h,{_type:"sessionStorage"})):(e.localStorage=e.extend({},h,{_type:"localCookieStorage"}),e.sessionStorage=e.extend({},h,{_type:"sessionCookieStorage"})),e.namespaceStorages={},e.removeAllStorages=function(t){e.localStorage.removeAll(t),e.sessionStorage.removeAll(t),e.cookieStorage&&e.cookieStorage.removeAll(t),t||(e.namespaceStorages={})}}); \ No newline at end of file From 75c7a630515dcfc1851246027fdf89ed7650cfd0 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Thu, 22 Aug 2019 17:39:15 -0500 Subject: [PATCH 357/841] MC-18945: Reading deprecated annotation in schema - fixed static failures on jenkins builds --- .../Framework/GraphQl/Config/GraphQlReaderTest.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index 6b7404b398501..8ea142cc73a9b 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -201,12 +201,13 @@ enumValues(includeDeprecated: true) { foreach ($sortFields as $sortField) { isset($searchTerm[$sortField]) && is_array($searchTerm[$sortField]) ? usort( - $searchTerm[$sortField], function ($a, $b) { - $cmpField = 'name'; - return isset($a[$cmpField]) && isset($b[$cmpField]) + $searchTerm[$sortField], + function ($a, $b) { + $cmpField = 'name'; + return isset($a[$cmpField]) && isset($b[$cmpField]) ? strcmp($a[$cmpField], $b[$cmpField]) : 0; } - ) : null; + ) : null; } $this->assertTrue( From 50101f197385552b2a7ab5ce4dc6cd91f52518bc Mon Sep 17 00:00:00 2001 From: Dmytro Poperechnyy <dpoperechnyy@magento.com> Date: Thu, 22 Aug 2019 18:27:36 -0500 Subject: [PATCH 358/841] MC-19245: jQuery cookie shim missing - Remove extra comma; --- app/code/Magento/Theme/view/base/requirejs-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Theme/view/base/requirejs-config.js b/app/code/Magento/Theme/view/base/requirejs-config.js index befaa5b7eb0fe..d7953ea60cd46 100644 --- a/app/code/Magento/Theme/view/base/requirejs-config.js +++ b/app/code/Magento/Theme/view/base/requirejs-config.js @@ -25,7 +25,7 @@ var config = { 'MutationObserver': ['es6-collections'], 'matchMedia': { 'exports': 'mediaCheck' - }, + } }, 'paths': { 'jquery/validate': 'jquery/jquery.validate', From 01edbdc5251ebd63fd921ee6042704534dda6f6f Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Fri, 23 Aug 2019 08:42:37 +0700 Subject: [PATCH 359/841] Resolve Mass Delete Subscribers doesn't have "confirm" message issue24248 --- app/code/Magento/Newsletter/i18n/en_US.csv | 1 + .../view/adminhtml/layout/newsletter_subscriber_block.xml | 1 + 2 files changed, 2 insertions(+) diff --git a/app/code/Magento/Newsletter/i18n/en_US.csv b/app/code/Magento/Newsletter/i18n/en_US.csv index 388b583f990b1..f390f6792635d 100644 --- a/app/code/Magento/Newsletter/i18n/en_US.csv +++ b/app/code/Magento/Newsletter/i18n/en_US.csv @@ -152,3 +152,4 @@ Store,Store "Store View","Store View" "Newsletter Subscriptions","Newsletter Subscriptions" "We have updated your subscription.","We have updated your subscription." +"Are you sure you want to delete the selected subscriber(s)?","Are you sure you want to delete the selected subscriber(s)?" diff --git a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml index e8600c2d49d7b..aef84c068100a 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml +++ b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml @@ -29,6 +29,7 @@ <item name="delete" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Delete</item> <item name="url" xsi:type="string">*/*/massDelete</item> + <item name="confirm" xsi:type="string" translate="true">Are you sure you want to delete the selected subscriber(s)?</item> </item> </argument> </arguments> From b76bd1dcf783ff7c261769a4a4c97e8e77bd3f77 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Fri, 23 Aug 2019 10:14:23 +0300 Subject: [PATCH 360/841] MC-18194: Saved Credit Card Feature with Vault is not displaying proper information of card in order information page --- app/code/Magento/Vault/Model/Method/Vault.php | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index 484f492ba872e..da1cf5bd3c67f 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -543,16 +543,22 @@ private function getPaymentExtensionAttributes(OrderPaymentInterface $payment) */ private function attachCreditCardInfo(OrderPaymentInterface $payment): void { - try { - $paymentToken = $payment->getExtensionAttributes() - ->getVaultPaymentToken(); - if ($paymentToken) { - $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); - if ($tokenDetails && is_array($tokenDetails)) { - $payment->addData($tokenDetails); - } - } - } catch (Exception $e) { + $paymentToken = $payment->getExtensionAttributes() + ->getVaultPaymentToken(); + if ($paymentToken === null) { + return; + } + + $tokenDetails = $paymentToken->getTokenDetails(); + if ($tokenDetails === null) { + return; + } + + if (is_string($tokenDetails)) { + $tokenDetails = $this->jsonSerializer->unserialize($paymentToken->getTokenDetails()); + } + if (is_array($tokenDetails)) { + $payment->addData($tokenDetails); } } From e9dd493dbd16c0e432ab854ca5af69466a301777 Mon Sep 17 00:00:00 2001 From: KrielkipNL <contact@krielkip.nl> Date: Fri, 23 Aug 2019 09:21:59 +0200 Subject: [PATCH 361/841] Update TopMenu.php Function _columnBrake return null when limit is smaller then $total. But _getHtml needs the result to be array. --- app/code/Magento/Theme/Block/Html/Topmenu.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Theme/Block/Html/Topmenu.php b/app/code/Magento/Theme/Block/Html/Topmenu.php index fd8aaa7708cf3..77b9144069502 100644 --- a/app/code/Magento/Theme/Block/Html/Topmenu.php +++ b/app/code/Magento/Theme/Block/Html/Topmenu.php @@ -133,7 +133,7 @@ protected function _countItems($items) * * @param Menu $items * @param int $limit - * @return array|void + * @return array * * @todo: Add Depth Level limit, and better logic for columns */ @@ -141,7 +141,7 @@ protected function _columnBrake($items, $limit) { $total = $this->_countItems($items); if ($total <= $limit) { - return; + return []; } $result[] = ['total' => $total, 'max' => (int)ceil($total / ceil($total / $limit))]; From 47eafc6e5a975887d4866a704fb88a430f663c51 Mon Sep 17 00:00:00 2001 From: dominic <dominic@wearekick.co.uk> Date: Fri, 23 Aug 2019 11:03:00 +0100 Subject: [PATCH 362/841] CustomerExtractor test update --- .../Customer/Model/CustomerExtractor.php | 7 +++-- .../Test/Unit/Model/CustomerExtractorTest.php | 26 +++++++++++-------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/app/code/Magento/Customer/Model/CustomerExtractor.php b/app/code/Magento/Customer/Model/CustomerExtractor.php index debdd001d4f79..e1ace7a98b65a 100644 --- a/app/code/Magento/Customer/Model/CustomerExtractor.php +++ b/app/code/Magento/Customer/Model/CustomerExtractor.php @@ -88,15 +88,18 @@ public function extract( $customerData, \Magento\Customer\Api\Data\CustomerInterface::class ); + $store = $this->storeManager->getStore(); + $storeId = $store->getId(); + if ($isGroupIdEmpty) { $customerDataObject->setGroupId( - $this->customerGroupManagement->getDefaultGroup($store->getId())->getId() + $this->customerGroupManagement->getDefaultGroup($storeId)->getId() ); } $customerDataObject->setWebsiteId($store->getWebsiteId()); - $customerDataObject->setStoreId($store->getId()); + $customerDataObject->setStoreId($storeId); return $customerDataObject; } diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php index 351c3435c73fc..ac8c60a23db80 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php @@ -3,10 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Test\Unit\Model; use Magento\Customer\Model\CustomerExtractor; +/** + * Unit test CustomerExtractorTest + */ class CustomerExtractorTest extends \PHPUnit\Framework\TestCase { /** @var CustomerExtractor */ @@ -137,19 +141,19 @@ public function testExtract() $this->storeManager->expects($this->once()) ->method('getStore') ->willReturn($this->store); - $this->store->expects($this->exactly(2)) - ->method('getId') - ->willReturn(1); - $this->customerGroupManagement->expects($this->once()) - ->method('getDefaultGroup') - ->with(1) - ->willReturn($this->customerGroup); - $this->customerGroup->expects($this->once()) + $this->store->expects($this->once()) ->method('getId') ->willReturn(1); - $this->customerData->expects($this->once()) - ->method('setGroupId') - ->with(1); +// $this->customerGroupManagement->expects($this->once()) +// ->method('getDefaultGroup') +// ->with(1) +// ->willReturn($this->customerGroup); +// $this->customerGroup->expects($this->once()) +// ->method('getId') +// ->willReturn(1); +// $this->customerData->expects($this->once()) +// ->method('setGroupId') +// ->with(1); $this->store->expects($this->once()) ->method('getWebsiteId') ->willReturn(1); From 746add61c4a0718fbe7b842a350734850de751ca Mon Sep 17 00:00:00 2001 From: Luke Rodgers <lr@amp.co> Date: Fri, 23 Aug 2019 11:46:12 +0100 Subject: [PATCH 363/841] Make isLoading private with no prefix --- app/code/Magento/Checkout/Model/Session.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Checkout/Model/Session.php b/app/code/Magento/Checkout/Model/Session.php index 571467cbc9571..4cc91dc60ea58 100644 --- a/app/code/Magento/Checkout/Model/Session.php +++ b/app/code/Magento/Checkout/Model/Session.php @@ -48,7 +48,7 @@ class Session extends \Magento\Framework\Session\SessionManager * * @var bool */ - protected $_isLoading = false; + private $isLoading = false; /** * Loaded order instance @@ -219,10 +219,10 @@ public function getQuote() $this->_eventManager->dispatch('custom_quote_process', ['checkout_session' => $this]); if ($this->_quote === null) { - if ($this->_isLoading) { + if ($this->isLoading) { throw new \LogicException("Infinite loop detected, review the trace for the looping path"); } - $this->_isLoading = true; + $this->isLoading = true; $quote = $this->quoteFactory->create(); if ($this->getQuoteId()) { try { @@ -275,7 +275,7 @@ public function getQuote() $quote->setStore($this->_storeManager->getStore()); $this->_quote = $quote; - $this->_isLoading = false; + $this->isLoading = false; } if (!$this->isQuoteMasked() && !$this->_customerSession->isLoggedIn() && $this->getQuoteId()) { From b92e1eb4d5d756f392e92f7c7848cfbd7ae3bcff Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Fri, 23 Aug 2019 13:44:33 +0200 Subject: [PATCH 364/841] MC-18221: Fix static tests --- .../Magento/Backup/Controller/Adminhtml/Index/Rollback.php | 3 ++- .../Controller/Adminhtml/System/Design/Theme/UploadJs.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php index f4037476f5e7b..1d231ab6e5db1 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php @@ -127,7 +127,8 @@ public function execute() $adminSession->destroy(); $response->setRedirectUrl($this->getUrl('*')); - // phpcs:disable Magento2.Exceptions.ThrowCatch - Required Refactoring see MC-19410 + // Required Refactoring see MC-19410 + // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Backup\Exception\CantLoadSnapshot $e) { $errorMsg = __('We can\'t find the backup file.'); } catch (\Magento\Framework\Backup\Exception\FtpConnectionFailed $e) { diff --git a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php index a1fcc74546700..b3217f8bc22b7 100644 --- a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php +++ b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php @@ -52,7 +52,8 @@ public function execute() \Magento\Framework\View\Design\Theme\Customization\File\Js::TYPE ); $result = ['error' => false, 'files' => $customization->generateFileInfo($customJsFiles)]; - // phpcs:disable Magento2.Exceptions.ThrowCatch - Required Refactoring see MC-19410 + // Required Refactoring see MC-19410 + // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { $result = ['error' => true, 'message' => $e->getMessage()]; } catch (\Exception $e) { From eb71581417ec022baddcf5bd7907b11ffc420789 Mon Sep 17 00:00:00 2001 From: dominic <dominic@wearekick.co.uk> Date: Fri, 23 Aug 2019 12:47:22 +0100 Subject: [PATCH 365/841] comment update --- app/code/Magento/Customer/Model/CustomerExtractor.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Model/CustomerExtractor.php b/app/code/Magento/Customer/Model/CustomerExtractor.php index e1ace7a98b65a..6c454ba4c3b8f 100644 --- a/app/code/Magento/Customer/Model/CustomerExtractor.php +++ b/app/code/Magento/Customer/Model/CustomerExtractor.php @@ -1,9 +1,9 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Customer\Model; use Magento\Customer\Api\CustomerMetadataInterface; @@ -11,6 +11,9 @@ use Magento\Customer\Api\GroupManagementInterface; use Magento\Framework\App\RequestInterface; +/** + * Customer Extractor model. + */ class CustomerExtractor { /** From 288a7c698a3e3c7ad732ec997ec8707fefa20ab1 Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Fri, 23 Aug 2019 14:01:05 +0200 Subject: [PATCH 366/841] MC-18221: Fix functional tests missing CurlTransport class --- .../Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php index 5b561708be12e..5999b52141f05 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php @@ -8,6 +8,7 @@ use Magento\Mtf\Config\DataInterface; use Magento\Mtf\Util\Protocol\CurlInterface; +use Magento\Mtf\Util\Protocol\CurlTransport; /** * Curl transport on backend. From f236c1c17850c4e64439a077f548c8778c944e1c Mon Sep 17 00:00:00 2001 From: Tomash Khamlai <tomash.khamlai@gmail.com> Date: Fri, 23 Aug 2019 15:01:43 +0300 Subject: [PATCH 367/841] Cover validation for Email Text Length Limit field and check that provided tip is truthful --- .../setEmailTextLengthLimitActionGroup.xml | 21 ++++++ .../Wishlist/Test/Mftf/Data/WishlistData.xml | 3 + .../Section/WishListShareOptionsSection.xml | 19 +++++ ...ishListShareOptionsInputValidationTest.xml | 72 +++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100755 app/code/Magento/Wishlist/Test/Mftf/ActionGroup/setEmailTextLengthLimitActionGroup.xml mode change 100644 => 100755 app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml create mode 100755 app/code/Magento/Wishlist/Test/Mftf/Section/WishListShareOptionsSection.xml create mode 100755 app/code/Magento/Wishlist/Test/Mftf/Test/AdminCustomerWishListShareOptionsInputValidationTest.xml diff --git a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/setEmailTextLengthLimitActionGroup.xml b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/setEmailTextLengthLimitActionGroup.xml new file mode 100755 index 0000000000000..685e1ab9f8714 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/setEmailTextLengthLimitActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="setEmailTextLengthLimitActionGroup"> + <arguments> + <argument name="emailTextLengthLimit" defaultValue="Wishlist.default_email_text_length_limit" type="string"/> + </arguments> + <amOnPage url="{{AdminCustomerWishlistConfigurationPage.url}}" stepKey="navigateToWishListConfigurationPage"/> + <conditionalClick selector="{{WishListShareOptionsSection.shareOptionsTabIsNotExpanded}}" dependentSelector="{{WishListShareOptionsSection.shareOptionsTabIsExpanded}}" visible="false" stepKey="expandTabInNotAlreadyExpanded"/> + <uncheckOption selector="{{WishListShareOptionsSection.useSystemValueForWishListEmailTextLimit}}" stepKey="uncheckUseSystemValueForWishListEmailTextLimit"/> + <fillField selector="{{WishListShareOptionsSection.emailTextLengthLimitInput}}" userInput="{{emailTextLengthLimit}}" stepKey="enterWishListTextLengthLimit"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="tryToSaveWishListConfig"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml old mode 100644 new mode 100755 index a8220ad0cfca3..4a25a8d449dd3 --- a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml @@ -14,5 +14,8 @@ <var key="customer_password" entityType="customer" entityKey="password"/> <data key="shareInfo_emails">JohnDoe123456789@example.com,JohnDoe987654321@example.com,JohnDoe123456abc@example.com</data> <data key="shareInfo_message">Sharing message.</data> + <data key="default_email_text_length_limit">255</data> + <data key="min_email_text_length_limit">1</data> + <data key="max_email_text_length_limit">10000</data> </entity> </entities> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/WishListShareOptionsSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/WishListShareOptionsSection.xml new file mode 100755 index 0000000000000..b69740572cea6 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Section/WishListShareOptionsSection.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="WishListShareOptionsSection"> + <element name="shareOptionsTab" type="button" selector="#wishlist_email-head"/> + <element name="shareOptionsTabIsExpanded" type="button" selector="#wishlist_email-head.open"/> + <element name="shareOptionsTabIsNotExpanded" type="button" selector="#wishlist_email-head"/> + <element name="emailTextLengthLimitInput" type="input" selector="#wishlist_email_text_limit"/> + <element name="emailTextLengthLimitMessage" type="text" selector="#wishlist_email_text_limit-error"/> + <element name="useSystemValueForWishListEmailTextLimit" type="checkbox" selector="#wishlist_email_text_limit_inherit"/> + </section> +</sections> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminCustomerWishListShareOptionsInputValidationTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminCustomerWishListShareOptionsInputValidationTest.xml new file mode 100755 index 0000000000000..da51bdf917e37 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminCustomerWishListShareOptionsInputValidationTest.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCustomerWishListShareOptionsInputValidationTest"> + <annotations> + <features value="Wishlist"/> + <stories value="MAGETWO-8709"/> + <group value="wishlist"/> + <title value="When user tries to set the Email Text Length Limit higher then 10,000 then validation message occurs"/> + <description value="When user tries to set the Email Text Length Limit higher then 10,000 then validation message occurs"/> + <severity value="AVERAGE"/> + <testCaseId value="N/a"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + </before> + <after> + <actionGroup ref="setEmailTextLengthLimitActionGroup" stepKey="rollbackEmailTextLengthLimit"> + <argument name="emailTextLengthLimit" value="{{Wishlist.default_email_text_length_limit}}"/> + </actionGroup> + <checkOption selector="{{WishListShareOptionsSection.useSystemValueForWishListEmailTextLimit}}" stepKey="checkUseSystemValueForWishListEmailTextLimit"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + + <actionGroup ref="setEmailTextLengthLimitActionGroup" stepKey="setEmailTextLengthLimitToMin"> + <argument name="emailTextLengthLimit" value="{{Wishlist.min_email_text_length_limit}}"/> + </actionGroup> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeSuccessMessageForMinimum"/> + <grabValueFrom selector="{{WishListShareOptionsSection.emailTextLengthLimitInput}}" stepKey="minimumWishListTextLengthLimit"/> + <assertEquals stepKey="AssertMinimumTextLengthLimitIsApplied"> + <expectedResult type="string">{{Wishlist.min_email_text_length_limit}}</expectedResult> + <actualResult type="variable">minimumWishListTextLengthLimit</actualResult> + </assertEquals> + + <actionGroup ref="setEmailTextLengthLimitActionGroup" stepKey="setEmailTextLengthLimitToMax"> + <argument name="emailTextLengthLimit" value="{{Wishlist.max_email_text_length_limit}}"/> + </actionGroup> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeSuccessMessageForMaximum"/> + <grabValueFrom selector="{{WishListShareOptionsSection.emailTextLengthLimitInput}}" stepKey="maximumWishListTextLengthLimit"/> + <assertEquals stepKey="AssertMaximumTextLengthLimitIsApplied"> + <expectedResult type="string">{{Wishlist.max_email_text_length_limit}}</expectedResult> + <actualResult type="variable">maximumWishListTextLengthLimit</actualResult> + </assertEquals> + + <actionGroup ref="setEmailTextLengthLimitActionGroup" stepKey="setEmailTextLengthLimitToLowerThanMin"> + <argument name="emailTextLengthLimit" value="0"/> + </actionGroup> + <dontSee selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="dontSeeSuccessMessageForLowerThanMinimum"/> + <grabTextFrom selector="{{WishListShareOptionsSection.emailTextLengthLimitMessage}}" stepKey="enterWishListTextLengthLimitLowerThanMinimum"/> + <assertEquals stepKey="AssertTextLengthLimitIsNotAppliedWhenLowerThanMinimum"> + <expectedResult type="string">The value is not within the specified range.</expectedResult> + <actualResult type="variable">enterWishListTextLengthLimitLowerThanMinimum</actualResult> + </assertEquals> + + <actionGroup ref="setEmailTextLengthLimitActionGroup" stepKey="setEmailTextLengthLimitToHigherThanMaximum"> + <argument name="emailTextLengthLimit" value="10001"/> + </actionGroup> + <dontSee selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="dontSeeSuccessMessageForHigherThanMaximum"/> + <grabTextFrom selector="{{WishListShareOptionsSection.emailTextLengthLimitMessage}}" stepKey="enterWishListTextLengthLimitHigherThanMaximum"/> + <assertEquals stepKey="AssertTextLengthLimitIsNotAppliedWhenHigherThanMaximum"> + <expectedResult type="string">The value is not within the specified range.</expectedResult> + <actualResult type="variable">enterWishListTextLengthLimitHigherThanMaximum</actualResult> + </assertEquals> + </test> +</tests> From 23014947d6a7a49665a30f4e813890f8ebe9929c Mon Sep 17 00:00:00 2001 From: dominic <dominic@wearekick.co.uk> Date: Fri, 23 Aug 2019 13:58:50 +0100 Subject: [PATCH 368/841] comment update --- app/code/Magento/Customer/Model/CustomerExtractor.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Customer/Model/CustomerExtractor.php b/app/code/Magento/Customer/Model/CustomerExtractor.php index 6c454ba4c3b8f..27718f64ae28f 100644 --- a/app/code/Magento/Customer/Model/CustomerExtractor.php +++ b/app/code/Magento/Customer/Model/CustomerExtractor.php @@ -63,6 +63,8 @@ public function __construct( } /** + * Creates a Customer object populated with the given form code and request data. + * * @param string $formCode * @param RequestInterface $request * @param array $attributeValues From 95eef9f87a1d9d631fac3e689d1cd8603bc57f0c Mon Sep 17 00:00:00 2001 From: dominic <dominic@wearekick.co.uk> Date: Fri, 23 Aug 2019 14:22:08 +0100 Subject: [PATCH 369/841] comment update --- app/code/Magento/Customer/Model/CustomerExtractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Model/CustomerExtractor.php b/app/code/Magento/Customer/Model/CustomerExtractor.php index 27718f64ae28f..5d6f3245a0439 100644 --- a/app/code/Magento/Customer/Model/CustomerExtractor.php +++ b/app/code/Magento/Customer/Model/CustomerExtractor.php @@ -64,7 +64,7 @@ public function __construct( /** * Creates a Customer object populated with the given form code and request data. - * + * * @param string $formCode * @param RequestInterface $request * @param array $attributeValues From c601181318dbd9fc4afa2c0d6064b6d12414d371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 23 Aug 2019 16:10:10 +0200 Subject: [PATCH 370/841] Fix #7200 - Configurable Products dropdown not showing pricing --- .../view/frontend/web/js/configurable.js | 76 +++++++++++++------ 1 file changed, 54 insertions(+), 22 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index d11260ca66373..120de93705819 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -123,6 +123,8 @@ define([ if (this.options.spConfig.inputsInitialized) { this._setValuesByAttribute(); } + + this._setInitialOptionsLabels(); }, /** @@ -158,6 +160,18 @@ define([ }, this)); }, + /** + * Set additional field with initial label to be used when switching between options with different prices. + * @private + */ + _setInitialOptionsLabels: function () { + $.each(this.options.spConfig.attributes, $.proxy(function (index, element) { + $.each(element.options, $.proxy(function (optIndex, optElement) { + this.options.spConfig.attributes[index].options[optIndex].label_initial = optElement.label; + }, this)); + }, this)); + }, + /** * Set up .on('change') events for each option element to configure the option. * @private @@ -371,6 +385,8 @@ define([ prevConfig, index = 1, allowedProducts, + allowedProductsByOption, + allowedProductsAll, i, j, finalPrice = parseFloat(this.options.spConfig.prices.finalPrice.amount), @@ -379,7 +395,8 @@ define([ optionPrices = this.options.spConfig.optionPrices, allowedOptions = [], indexKey, - allowedProductMinPrice; + allowedProductMinPrice, + allowedProductsAllMinPrice; this._clearSelect(element); element.options[0] = new Option('', ''); @@ -398,35 +415,50 @@ define([ } } - for (i = 0; i < options.length; i++) { - allowedProducts = []; - optionPriceDiff = 0; + if (prevConfig) { + allowedProductsByOption = {}; + allowedProductsAll = []; - /* eslint-disable max-depth */ - if (prevConfig) { + for (i = 0; i < options.length; i++) { + /* eslint-disable max-depth */ for (j = 0; j < options[i].products.length; j++) { // prevConfig.config can be undefined if (prevConfig.config && prevConfig.config.allowedProducts && prevConfig.config.allowedProducts.indexOf(options[i].products[j]) > -1) { - allowedProducts.push(options[i].products[j]); + if (!allowedProductsByOption[i]) { + allowedProductsByOption[i] = []; + } + allowedProductsByOption[i].push(options[i].products[j]); + allowedProductsAll.push(options[i].products[j]); } } - } else { - allowedProducts = options[i].products.slice(0); - - if (typeof allowedProducts[0] !== 'undefined' && - typeof optionPrices[allowedProducts[0]] !== 'undefined') { - allowedProductMinPrice = this._getAllowedProductWithMinPrice(allowedProducts); - optionFinalPrice = parseFloat(optionPrices[allowedProductMinPrice].finalPrice.amount); - optionPriceDiff = optionFinalPrice - finalPrice; - - if (optionPriceDiff !== 0) { - options[i].label = options[i].label + ' ' + priceUtils.formatPrice( - optionPriceDiff, - this.options.priceFormat, - true); - } + } + + if (typeof allowedProductsAll[0] !== 'undefined' && + typeof optionPrices[allowedProductsAll[0]] !== 'undefined') { + allowedProductsAllMinPrice = this._getAllowedProductWithMinPrice(allowedProductsAll); + finalPrice = parseFloat(optionPrices[allowedProductsAllMinPrice].finalPrice.amount); + } + } + + for (i = 0; i < options.length; i++) { + allowedProducts = prevConfig ? allowedProductsByOption[i] : options[i].products.slice(0); + optionPriceDiff = 0; + + if (typeof allowedProducts[0] !== 'undefined' && + typeof optionPrices[allowedProducts[0]] !== 'undefined') { + allowedProductMinPrice = this._getAllowedProductWithMinPrice(allowedProducts); + optionFinalPrice = parseFloat(optionPrices[allowedProductMinPrice].finalPrice.amount); + optionPriceDiff = optionFinalPrice - finalPrice; + + options[i].label = options[i].label_initial; + if (optionPriceDiff !== 0) { + options[i].label += ' ' + priceUtils.formatPrice( + optionPriceDiff, + this.options.priceFormat, + true + ); } } From 10cfb2620bea8d1da250fe2c94edb0be2bd0ed55 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Fri, 23 Aug 2019 17:18:52 +0300 Subject: [PATCH 371/841] MC-19260: Coupon code removed during tax/shipping calculation on checkout --- .../Magento/SalesRule/Model/Validator.php | 4 +- .../Rule/Action/Discount/CartFixedTest.php | 56 +++++++++++++ ..._fixed_discount_subtotal_with_discount.php | 58 ++++++++++++++ .../SalesRule/_files/quote_with_coupon.php | 79 +++++++++++++++++++ 4 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php diff --git a/app/code/Magento/SalesRule/Model/Validator.php b/app/code/Magento/SalesRule/Model/Validator.php index b272aa73af42a..dad35165051ce 100644 --- a/app/code/Magento/SalesRule/Model/Validator.php +++ b/app/code/Magento/SalesRule/Model/Validator.php @@ -243,9 +243,9 @@ public function canApplyRules(AbstractItem $item) public function reset(Address $address) { $this->validatorUtility->resetRoundingDeltas(); + $address->setBaseSubtotalWithDiscount($address->getBaseSubtotal()); + $address->setSubtotalWithDiscount($address->getSubtotal()); if ($this->_isFirstTimeResetRun) { - $address->setBaseSubtotalWithDiscount($address->getBaseSubtotal()); - $address->setSubtotalWithDiscount($address->getSubtotal()); $address->setAppliedRuleIds(''); $address->getQuote()->setAppliedRuleIds(''); $this->_isFirstTimeResetRun = false; 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 694310f2cbc89..21f8d4a45432f 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 @@ -10,15 +10,20 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ProductRepository; +use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Quote\Api\Data\CartItemInterface; use Magento\Quote\Api\GuestCartItemRepositoryInterface; use Magento\Quote\Api\GuestCartManagementInterface; use Magento\Quote\Api\GuestCartTotalRepositoryInterface; use Magento\Quote\Api\GuestCouponManagementInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; use Magento\TestFramework\Helper\Bootstrap; /** * Tests for Magento\SalesRule\Model\Rule\Action\Discount\CartFixed. + * + * @magentoAppArea frontend */ class CartFixedTest extends \PHPUnit\Framework\TestCase { @@ -37,11 +42,17 @@ class CartFixedTest extends \PHPUnit\Framework\TestCase */ private $couponManagement; + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + /** * @inheritdoc */ protected function setUp() { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->cartManagement = Bootstrap::getObjectManager()->create(GuestCartManagementInterface::class); $this->couponManagement = Bootstrap::getObjectManager()->create(GuestCouponManagementInterface::class); $this->cartItemRepository = Bootstrap::getObjectManager()->create(GuestCartItemRepositoryInterface::class); @@ -82,6 +93,30 @@ public function testApplyFixedDiscount(array $productPrices): void $this->assertEquals($expectedDiscount, $total->getBaseDiscountAmount()); } + /** + * Applies fixed discount amount on whole cart and created order with it + * + * @return void + * @magentoDataFixture Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php + * @magentoDataFixture Magento/SalesRule/_files/quote_with_coupon.php + * + */ + public function testOrderWithFixedDiscount(): void + { + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class); + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ + $quoteIdMask = $this->objectManager->create(\Magento\Quote\Model\QuoteIdMask::class); + $quote->load('test01', 'reserved_order_id'); + $quoteIdMask->load($quote->getId(), 'quote_id'); + Bootstrap::getInstance()->reinitialize(); + + $cartManagement = Bootstrap::getObjectManager()->create(GuestCartManagementInterface::class); + $cartManagement->placeOrder($quoteIdMask->getMaskedId()); + $order = $this->getOrder('test01'); + $this->assertEquals($quote->getGrandTotal(), $order->getGrandTotal()); + } + /** * @return array */ @@ -150,4 +185,25 @@ private function createProduct(float $price): ProductInterface return $productRepository->save($product); } + + /** + * Gets order entity by increment id. + * + * @param string $incrementId + * @return OrderInterface + */ + private function getOrder(string $incrementId): OrderInterface + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', $incrementId) + ->create(); + + /** @var OrderRepositoryInterface $repository */ + $repository = $this->objectManager->get(OrderRepositoryInterface::class); + $items = $repository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } } diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php new file mode 100644 index 0000000000000..04a91f2bdf55c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Customer\Model\GroupManagement; +use Magento\SalesRule\Api\CouponRepositoryInterface; +use Magento\SalesRule\Model\Coupon; +use Magento\SalesRule\Model\Rule; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$salesRule = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\SalesRule\Model\Rule::class); +$salesRule->setData( + [ + 'name' => '5$ fixed discount on whole cart', + 'is_active' => 1, + 'customer_group_ids' => [GroupManagement::NOT_LOGGED_IN_ID], + 'coupon_type' => Rule::COUPON_TYPE_SPECIFIC, + 'simple_action' => Rule::CART_FIXED_ACTION, + 'discount_amount' => 8, + 'discount_step' => 0, + 'stop_rules_processing' => 0, + 'website_ids' => [ + $objectManager->get(StoreManagerInterface::class)->getWebsite()->getId(), + ], + ] +); +$salesRule->getConditions()->loadArray([ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'any', + 'conditions' => + [ + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, + 'attribute' => 'base_subtotal_with_discount', + 'operator' => '>=', + 'value' => 9, + 'is_value_processed' => false + ], + ], +]); + +$salesRule->save(); + +// Create coupon and assign "5$ fixed discount" rule to this coupon. +$coupon = $objectManager->create(Coupon::class); +$coupon->setRuleId($salesRule->getId()) + ->setCode('CART_FIXED_DISCOUNT_5') + ->setType(0); +$objectManager->get(CouponRepositoryInterface::class)->save($coupon); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php new file mode 100644 index 0000000000000..e6148c3e2a4e2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Quote\Api\GuestCouponManagementInterface; +use Magento\TestFramework\Helper\Bootstrap; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('frontend'); +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId('simple') + ->setId(1) + ->setAttributeSetId(4) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice(10) + ->setTaxClassId(0) + ->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' => 100, + 'is_in_stock' => 1, + 'manage_stock' => 1, + ] + )->save(); + +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$product = $productRepository->get('simple'); + +$addressData = include __DIR__ . '/../../../Magento/Sales/_files/address_data.php'; +$billingAddress = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Quote\Model\Quote\Address::class, + ['data' => $addressData] +); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType('shipping'); + +$store = Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Store\Model\StoreManagerInterface::class) + ->getStore(); + +/** @var \Magento\Quote\Model\Quote $quote */ +$quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); +$quote->setCustomerIsGuest(true) + ->setStoreId($store->getId()) + ->setReservedOrderId('test01') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->addProduct($product); +$quote->getPayment()->setMethod('checkmo'); +$quote->getShippingAddress()->setShippingMethod('flatrate_flatrate')->setCollectShippingRates(true); +$quote->setIsMultiShipping('1'); +$quote->collectTotals(); + +$quoteRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Quote\Api\CartRepositoryInterface::class); +$quoteRepository->save($quote); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); + +$couponCode = 'CART_FIXED_DISCOUNT_5'; +$couponManagement = Bootstrap::getObjectManager()->create(GuestCouponManagementInterface::class); +$couponManagement->set($quoteIdMask->getMaskedId(), $couponCode); + + From bcd75f44e9c9e126571ddb126ea6a7bb3ab5c9fb Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Fri, 23 Aug 2019 10:16:40 -0500 Subject: [PATCH 372/841] Update sidebar.js --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index b1a88e15bd4ef..3a7fb17c31b8f 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -262,7 +262,7 @@ define([ productIds: [productData['product_id']] }); if (window.location.href === this.shoppingCartUrl) { - location.reload(); + window.location.reload(); } } }, From 32278f87966dbd64b7a84bd1684bffbe4b579417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 23 Aug 2019 17:30:22 +0200 Subject: [PATCH 373/841] Fix #7200 - static test fix --- .../ConfigurableProduct/view/frontend/web/js/configurable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index 120de93705819..4bed28132c7ea 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -451,8 +451,8 @@ define([ allowedProductMinPrice = this._getAllowedProductWithMinPrice(allowedProducts); optionFinalPrice = parseFloat(optionPrices[allowedProductMinPrice].finalPrice.amount); optionPriceDiff = optionFinalPrice - finalPrice; - options[i].label = options[i].label_initial; + if (optionPriceDiff !== 0) { options[i].label += ' ' + priceUtils.formatPrice( optionPriceDiff, From 5ae63959dd7a3046e9fb32cdf28038448e8ac7aa Mon Sep 17 00:00:00 2001 From: Scott Buchanan <sbuchanan@ripen.com> Date: Mon, 19 Aug 2019 14:24:19 -0400 Subject: [PATCH 374/841] use appropriate label for store switch "Store view" is a particular scope level and therefore shouldn't be used as the label for a dropdown which may include multiple scope levels. --- app/code/Magento/Backend/i18n/en_US.csv | 2 +- .../Backend/view/adminhtml/templates/store/switcher.phtml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv index bfedd56b14313..29fc5e9bc6ac4 100644 --- a/app/code/Magento/Backend/i18n/en_US.csv +++ b/app/code/Magento/Backend/i18n/en_US.csv @@ -258,7 +258,7 @@ Minute,Minute "To use this website you must first enable JavaScript in your browser.","To use this website you must first enable JavaScript in your browser." "This is only a demo store. You can browse and place orders, but nothing will be processed.","This is only a demo store. You can browse and place orders, but nothing will be processed." "Report an Issue","Report an Issue" -"Store View:","Store View:" +"Scope:","Scope:" "Stores Configuration","Stores Configuration" "Please confirm scope switching. All data that hasn't been saved will be lost.","Please confirm scope switching. All data that hasn't been saved will be lost." "Additional Cache Management","Additional Cache Management" diff --git a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml index da18bc183759b..df9323a7276df 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml @@ -9,7 +9,7 @@ <?php if ($websites = $block->getWebsites()) : ?> <div class="store-switcher store-view"> - <span class="store-switcher-label"><?= $block->escapeHtml(__('Store View:')) ?></span> + <span class="store-switcher-label"><?= $block->escapeHtml(__('Scope:')) ?></span> <div class="actions dropdown closable"> <input type="hidden" name="store_switcher" id="store_switcher" data-role="store-view-id" data-param="<?= $block->escapeHtmlAttr($block->getStoreVarName()) ?>" From ce673592342445b582697c2817b23e809cddaefa Mon Sep 17 00:00:00 2001 From: Rus0 <andonidominguez@wolfsellers.com> Date: Fri, 23 Aug 2019 11:56:35 -0500 Subject: [PATCH 375/841] Removed static root tree, Getting it from store manager --- .../Model/Resolver/CategoryTree.php | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 89d3805383e1a..65534572ac07a 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -27,7 +27,7 @@ class CategoryTree implements ResolverInterface const CATEGORY_INTERFACE = 'CategoryInterface'; /** - * @var \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree + * @var Products\DataProvider\CategoryTree */ private $categoryTree; @@ -41,19 +41,32 @@ class CategoryTree implements ResolverInterface */ private $checkCategoryIsActive; + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var int + */ + private $rootCategoryId = null; + /** * @param \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CheckCategoryIsActive $checkCategoryIsActive + * @param \Magento\Store\Model\StoreManagerInterface $storeManager */ public function __construct( - \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree, + Products\DataProvider\CategoryTree $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, - CheckCategoryIsActive $checkCategoryIsActive + CheckCategoryIsActive $checkCategoryIsActive, + \Magento\Store\Model\StoreManagerInterface $storeManager ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; $this->checkCategoryIsActive = $checkCategoryIsActive; + $this->storeManager = $storeManager; } /** @@ -61,15 +74,11 @@ public function __construct( * * @param array $args * @return int - * @throws GraphQlInputException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ private function getCategoryId(array $args) : int { - if (!isset($args['id'])) { - throw new GraphQlInputException(__('"id for category should be specified')); - } - - return (int)$args['id']; + return isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId(); } /** @@ -82,7 +91,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $rootCategoryId = $this->getCategoryId($args); - if ($rootCategoryId !== Category::TREE_ROOT_ID) { + if ($rootCategoryId !== $this->getRootCategoryId() && $rootCategoryId > 0) { $this->checkCategoryIsActive->execute($rootCategoryId); } $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); @@ -94,4 +103,18 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $result = $this->extractDataFromCategoryTree->execute($categoriesTree); return current($result); } + + /** + * Get Root Category Id + * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getRootCategoryId() + { + if ($this->rootCategoryId == null) { + $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); + } + + return $this->rootCategoryId; + } } From 1c2dd94a6ac0dd00020ec4793c92d9f663717d79 Mon Sep 17 00:00:00 2001 From: Rus0 <andonidominguez@wolfsellers.com> Date: Fri, 23 Aug 2019 12:19:18 -0500 Subject: [PATCH 376/841] Adding root_category_id to storeconfig --- .../Model/Resolver/CategoryRoot.php | 86 +++++++++++++++++++ .../CatalogGraphQl/etc/schema.graphqls | 1 + 2 files changed, 87 insertions(+) create mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php new file mode 100644 index 0000000000000..a906a0ff14f70 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver; + +use Magento\Catalog\Model\Category; +use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Query\ResolverInterface; + +/** + * Category tree field resolver, used for GraphQL request processing. + */ +class CategoryRoot implements ResolverInterface +{ + /** + * Name of type in GraphQL + */ + const CATEGORY_INTERFACE = 'CategoryInterface'; + + /** + * @var Products\DataProvider\CategoryTree + */ + private $categoryTree; + + /** + * @var ExtractDataFromCategoryTree + */ + private $extractDataFromCategoryTree; + + /** + * @var CheckCategoryIsActive + */ + private $checkCategoryIsActive; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @var int + */ + private $rootCategoryId = null; + + /** + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + */ + public function __construct( + \Magento\Store\Model\StoreManagerInterface $storeManager + ) { + $this->storeManager = $storeManager; + } + + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + + return $this->getRootCategoryId(); + } + + /** + * Get Root Category Id + * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function getRootCategoryId() + { + if ($this->rootCategoryId == null) { + $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); + } + + return $this->rootCategoryId; + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index ea56faf94408e..554ad4b45cf99 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -416,6 +416,7 @@ type StoreConfig @doc(description: "The type contains information about a store grid_per_page : Int @doc(description: "Products per Page on Grid Default Value.") list_per_page : Int @doc(description: "Products per Page on List Default Value.") catalog_default_sort_by : String @doc(description: "Default Sort By.") + root_category_id: Int @doc(description: "The ID ot root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryRoot") } type ProductVideo @doc(description: "Contains information about a product video.") implements MediaGalleryInterface { From 894fae5c4d528dbb3cdcb6911a7e8f64da218336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 23 Aug 2019 19:28:28 +0200 Subject: [PATCH 377/841] Fix #7200 - static test fix --- .../ConfigurableProduct/view/frontend/web/js/configurable.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index 4bed28132c7ea..ae564610e4b0b 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -167,7 +167,7 @@ define([ _setInitialOptionsLabels: function () { $.each(this.options.spConfig.attributes, $.proxy(function (index, element) { $.each(element.options, $.proxy(function (optIndex, optElement) { - this.options.spConfig.attributes[index].options[optIndex].label_initial = optElement.label; + this.options.spConfig.attributes[index].options[optIndex].initialLabel = optElement.label; }, this)); }, this)); }, @@ -451,7 +451,7 @@ define([ allowedProductMinPrice = this._getAllowedProductWithMinPrice(allowedProducts); optionFinalPrice = parseFloat(optionPrices[allowedProductMinPrice].finalPrice.amount); optionPriceDiff = optionFinalPrice - finalPrice; - options[i].label = options[i].label_initial; + options[i].label = options[i].initialLabel; if (optionPriceDiff !== 0) { options[i].label += ' ' + priceUtils.formatPrice( From 10f880c68185228c2bdc3598203deeed47094f65 Mon Sep 17 00:00:00 2001 From: Will Beltran <wbeltran@wolfsellers.com> Date: Fri, 23 Aug 2019 13:39:46 -0500 Subject: [PATCH 378/841] Correction in the shipping method if it is empty it no longer shows the indefinite word --- .../view/frontend/web/js/view/shipping-information.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js index b2367fbda5412..128366b6ce66f 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js @@ -29,8 +29,15 @@ define([ */ getShippingMethodTitle: function () { var shippingMethod = quote.shippingMethod(); + var shippingMethodTitle = ''; - return shippingMethod ? shippingMethod['carrier_title'] + ' - ' + shippingMethod['method_title'] : ''; + if (typeof shippingMethod['method_title'] !== 'undefined') { + shippingMethodTitle = ' - ' + shippingMethod['method_title']; + } + + return shippingMethod ? shippingMethod['carrier_title'] + shippingMethodTitle : shippingMethod['carrier_title']; + + //return shippingMethod ? shippingMethod['carrier_title'] + ' - ' + shippingMethod['method_title'] : ''; }, /** From 3ad13cd43259fb27354e33c5a94badf1b2f052eb Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Fri, 23 Aug 2019 13:54:21 -0500 Subject: [PATCH 379/841] MC-19572: Fix filtering by attribute of type select/multiselect using filter input type "in" --- .../Model/Adapter/Mysql/Filter/Preprocessor.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php index c758e773f43c1..37d2dda886259 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php @@ -165,7 +165,10 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu $this->customerSession->getCustomerGroupId() ); } elseif ($filter->getField() === 'category_ids') { - return 'category_ids_index.category_id = ' . (int) $filter->getValue(); + return $this->connection->quoteInto( + 'category_ids_index.category_id in (?)', + $filter->getValue() + ); } elseif ($attribute->isStatic()) { $alias = $this->aliasResolver->getAlias($filter); $resultQuery = str_replace( From 61286c86b6026b07bc214c6ead5d96128396668e Mon Sep 17 00:00:00 2001 From: Rus0 <andonidominguez@wolfsellers.com> Date: Fri, 23 Aug 2019 14:04:25 -0500 Subject: [PATCH 380/841] Functional Testing --- .../Magento/GraphQl/Catalog/CategoryTest.php | 74 +++++++++++++++++++ .../GraphQl/Catalog/StoreConfigTest.php | 4 +- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index df8e399ce6c61..fdd19cdfbb04a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -113,6 +113,80 @@ public function testCategoriesTree() ); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testRootCategoryTree() + { + $query = <<<QUERY +{ + category { + id + level + description + path + path_in_store + product_count + url_key + url_path + children { + id + description + available_sort_by + default_sort_by + image + level + children { + id + filter_price_range + description + image + meta_keywords + level + is_anchor + children { + level + id + } + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $responseDataObject = new DataObject($response); + //Some sort of smoke testing + self::assertEquals( + 'Its a description of Test Category 1.2', + $responseDataObject->getData('category/children/0/children/1/description') + ); + self::assertEquals( + 'default-category', + $responseDataObject->getData('category/url_key') + ); + self::assertEquals( + [], + $responseDataObject->getData('category/children/0/available_sort_by') + ); + self::assertEquals( + 'name', + $responseDataObject->getData('category/children/0/default_sort_by') + ); + self::assertCount( + 7, + $responseDataObject->getData('category/children') + ); + self::assertCount( + 2, + $responseDataObject->getData('category/children/0/children') + ); + self::assertEquals( + 13, + $responseDataObject->getData('category/children/0/children/1/id') + ); + } + /** * @magentoApiDataFixture Magento/Catalog/_files/categories.php */ diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/StoreConfigTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/StoreConfigTest.php index 21e608da4800a..7a30023c89f7e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/StoreConfigTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/StoreConfigTest.php @@ -40,7 +40,8 @@ public function testGetStoreConfig() list_per_page_values, grid_per_page, list_per_page, - catalog_default_sort_by + catalog_default_sort_by, + root_category_id } } QUERY; @@ -56,5 +57,6 @@ public function testGetStoreConfig() $this->assertEquals('8', $response['storeConfig']['list_per_page_values']); $this->assertEquals(8, $response['storeConfig']['list_per_page']); $this->assertEquals('asc', $response['storeConfig']['catalog_default_sort_by']); + $this->assertEquals(2, $response['storeConfig']['root_category_id']); } } From 775eab4a89135724411e725083647f40140ae959 Mon Sep 17 00:00:00 2001 From: Will Beltran <wbeltran@wolfsellers.com> Date: Fri, 23 Aug 2019 14:29:36 -0500 Subject: [PATCH 381/841] Remove line comment --- .../Checkout/view/frontend/web/js/view/shipping-information.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js index 128366b6ce66f..a7e27aa3e4ee7 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js @@ -36,8 +36,6 @@ define([ } return shippingMethod ? shippingMethod['carrier_title'] + shippingMethodTitle : shippingMethod['carrier_title']; - - //return shippingMethod ? shippingMethod['carrier_title'] + ' - ' + shippingMethod['method_title'] : ''; }, /** From 98461385ebcc16df9094918b79c4401564098f58 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin <dyushkin@adobe.com> Date: Fri, 23 Aug 2019 13:02:42 -0500 Subject: [PATCH 382/841] MC-19114: Authorize.net "Qty to Refund" not editable --- .../RefundTransactionStrategyCommand.php | 28 +++- .../ClosePartialTransactionHandler.php | 27 ++++ .../Response/CloseTransactionHandler.php | 14 +- .../RefundTransactionStrategyCommandTest.php | 120 ++++++++++++++---- .../AuthorizenetAcceptjs/etc/config.xml | 1 + .../Magento/AuthorizenetAcceptjs/etc/di.xml | 3 +- .../AuthorizenetAcceptjs/i18n/en_US.csv | 1 + .../Fixture/full_order_with_capture.php | 26 +++- .../Command/RefundSettledCommandTest.php | 34 ++++- .../_files/full_order.php | 16 ++- 10 files changed, 230 insertions(+), 40 deletions(-) create mode 100644 app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/ClosePartialTransactionHandler.php diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php index 53a1f13fa8786..74f01494d82bf 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php @@ -59,6 +59,7 @@ public function execute(array $commandSubject): void * @param array $commandSubject * @return string * @throws CommandException + * @throws \Magento\Framework\Exception\NotFoundException */ private function getCommand(array $commandSubject): string { @@ -66,12 +67,35 @@ private function getCommand(array $commandSubject): string ->execute($commandSubject) ->get(); - if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') { + if ($this->canVoid($details, $commandSubject)) { return self::VOID; - } elseif ($details['transaction']['transactionStatus'] !== 'settledSuccessfully') { + } + + if ($details['transaction']['transactionStatus'] !== 'settledSuccessfully') { throw new CommandException(__('This transaction cannot be refunded with its current status.')); } return self::REFUND; } + + /** + * Checks if void command can be performed. + * + * @param array $details + * @param array $commandSubject + * @return bool + * @throws CommandException + */ + private function canVoid(array $details, array $commandSubject) :bool + { + if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') { + if ((float) $details['transaction']['authAmount'] !== (float) $commandSubject['amount']) { + throw new CommandException(__('Transaction has not been settled yet, partial void is not available.')); + } + + return true; + } + + return false; + } } diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/ClosePartialTransactionHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/ClosePartialTransactionHandler.php new file mode 100644 index 0000000000000..fd8af3d28c4d4 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/ClosePartialTransactionHandler.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Response; + +use Magento\Sales\Model\Order\Payment; + +/** + * Determines that parent transaction should be close for partial refund operation. + */ +class ClosePartialTransactionHandler extends CloseTransactionHandler +{ + /** + * Whether parent transaction should be closed. + * + * @param Payment $payment + * @return bool + */ + public function shouldCloseParentTransaction(Payment $payment) + { + return !(bool)$payment->getCreditmemo()->getInvoice()->canRefund(); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php index acbde62bacd77..fa9bf55462111 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php @@ -47,7 +47,19 @@ public function handle(array $handlingSubject, array $response): void if ($payment instanceof Payment) { $payment->setIsTransactionClosed($this->closeTransaction); - $payment->setShouldCloseParentTransaction(true); + $payment->setShouldCloseParentTransaction($this->shouldCloseParentTransaction($payment)); } } + + /** + * Whether parent transaction should be closed. + * + * @param Payment $payment + * @return bool + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function shouldCloseParentTransaction(Payment $payment) + { + return true; + } } diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php index df6d89d7bc585..9fad69c899a3f 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php @@ -62,20 +62,75 @@ public function testCommandWillVoidWhenTransactionIsPendingSettlement() ->method('execute'); $this->commandPoolMock->method('get') - ->willReturnMap([ - ['get_transaction_details', $this->transactionDetailsCommandMock], - ['void', $this->commandMock] - ]); + ->willReturnMap( + [ + [ + 'get_transaction_details', + $this->transactionDetailsCommandMock + ], + [ + 'void', + $this->commandMock + ] + ] + ); $this->transactionResultMock->method('get') - ->willReturn([ - 'transaction' => [ - 'transactionStatus' => 'capturedPendingSettlement' + ->willReturn( + [ + 'transaction' => [ + 'transactionStatus' => 'capturedPendingSettlement', + 'authAmount' => '20.19', + ] ] - ]); + ); $buildSubject = [ - 'foo' => '123' + 'foo' => '123', + 'amount' => '20.19', + ]; + + $this->transactionDetailsCommandMock->expects($this->once()) + ->method('execute') + ->with($buildSubject) + ->willReturn($this->transactionResultMock); + + $this->command->execute($buildSubject); + } + + /** + * @expectedException \Magento\Payment\Gateway\Command\CommandException + * @expectedExceptionMessage Transaction has not been settled yet, partial void is not available. + */ + public function testCommandWillThrowExceptionWhenVoidTransactionIsPartial() + { + // Assert command is executed + $this->commandMock->expects($this->never()) + ->method('execute'); + + $this->commandPoolMock->method('get') + ->willReturnMap( + [ + [ + 'get_transaction_details', + $this->transactionDetailsCommandMock + ], + ] + ); + + $this->transactionResultMock->method('get') + ->willReturn( + [ + 'transaction' => [ + 'transactionStatus' => 'capturedPendingSettlement', + 'authAmount' => '20.19', + ] + ] + ); + + $buildSubject = [ + 'foo' => '123', + 'amount' => '10.19', ]; $this->transactionDetailsCommandMock->expects($this->once()) @@ -93,17 +148,27 @@ public function testCommandWillRefundWhenTransactionIsSettled() ->method('execute'); $this->commandPoolMock->method('get') - ->willReturnMap([ - ['get_transaction_details', $this->transactionDetailsCommandMock], - ['refund_settled', $this->commandMock] - ]); + ->willReturnMap( + [ + [ + 'get_transaction_details', + $this->transactionDetailsCommandMock + ], + [ + 'refund_settled', + $this->commandMock + ] + ] + ); $this->transactionResultMock->method('get') - ->willReturn([ - 'transaction' => [ - 'transactionStatus' => 'settledSuccessfully' + ->willReturn( + [ + 'transaction' => [ + 'transactionStatus' => 'settledSuccessfully' + ] ] - ]); + ); $buildSubject = [ 'foo' => '123' @@ -128,16 +193,23 @@ public function testCommandWillThrowExceptionWhenTransactionIsInInvalidState() ->method('execute'); $this->commandPoolMock->method('get') - ->willReturnMap([ - ['get_transaction_details', $this->transactionDetailsCommandMock], - ]); + ->willReturnMap( + [ + [ + 'get_transaction_details', + $this->transactionDetailsCommandMock + ], + ] + ); $this->transactionResultMock->method('get') - ->willReturn([ - 'transaction' => [ - 'transactionStatus' => 'somethingIsWrong' + ->willReturn( + [ + 'transaction' => [ + 'transactionStatus' => 'somethingIsWrong' + ] ] - ]); + ); $buildSubject = [ 'foo' => '123' diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml index 7324421d3c14b..6fdbb98a78f8b 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml @@ -24,6 +24,7 @@ <can_capture_partial>0</can_capture_partial> <can_authorize>1</can_authorize> <can_refund>1</can_refund> + <can_refund_partial_per_invoice>1</can_refund_partial_per_invoice> <can_capture>1</can_capture> <can_void>1</can_void> <can_accept_payment>1</can_accept_payment> diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml index 145bcf22fd912..1bff19e15a65f 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml @@ -206,8 +206,7 @@ <arguments> <argument name="handlers" xsi:type="array"> <item name="transaction_id" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionIdHandler</item> - <item name="close_parent_transaction" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler</item> - <item name="close_transaction" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\CloseTransactionHandler</item> + <item name="close_transaction" xsi:type="string">Magento\AuthorizenetAcceptjs\Gateway\Response\ClosePartialTransactionHandler</item> </argument> </arguments> </virtualType> diff --git a/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv index da518301652f4..c6c2b0d6f1fb2 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv +++ b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv @@ -19,3 +19,4 @@ Authorize.net,Authorize.net "ccLast4","Last 4 Digits of Card" "There was an error while trying to process the refund.","There was an error while trying to process the refund." "This transaction cannot be refunded with its current status.","This transaction cannot be refunded with its current status." +"Transaction has not been settled yet, partial void is not available.","Transaction has not been settled yet, partial void is not available." diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php index 4ef5a4dd14c08..0b1e8196ef007 100644 --- a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php @@ -7,13 +7,16 @@ declare(strict_types=1); use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\Sales\Api\InvoiceRepositoryInterface; use Magento\Sales\Model\Order\Payment; use Magento\Sales\Model\OrderRepository; +use Magento\Sales\Model\Service\InvoiceService; use Magento\TestFramework\Helper\Bootstrap; use Magento\Sales\Api\TransactionRepositoryInterface; use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface as TransactionBuilder; +// phpcs:ignore Magento2.Security.IncludeFile.FoundIncludeFile $order = include __DIR__ . '/../_files/full_order.php'; $objectManager = Bootstrap::getObjectManager(); @@ -24,11 +27,32 @@ $payment->setAuthorizationTransaction(false); $payment->setParentTransactionId(4321); - /** @var OrderRepository $orderRepo */ $orderRepo = $objectManager->get(OrderRepository::class); $orderRepo->save($order); +/** @var InvoiceService $invoiceService */ +$invoiceService = $objectManager->get(InvoiceService::class); +$invoice = $invoiceService->prepareInvoice($order); +$invoice->setIncrementId('100000001'); +$invoice->register(); + +/** @var InvoiceRepositoryInterface $invoiceRepository */ +$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); +$invoice = $invoiceRepository->save($invoice); + + +/** @var \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory */ +$creditmemoFactory = $objectManager->get(\Magento\Sales\Model\Order\CreditmemoFactory::class); +$creditmemo = $creditmemoFactory->createByInvoice($invoice, $invoice->getData()); +$creditmemo->setOrder($order); +$creditmemo->setState(Magento\Sales\Model\Order\Creditmemo::STATE_OPEN); +$creditmemo->setIncrementId('100000001'); + +/** @var \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository */ +$creditmemoRepository = $objectManager->get(\Magento\Sales\Api\CreditmemoRepositoryInterface::class); +$creditmemoRepository->save($creditmemo); + /** @var TransactionBuilder $transactionBuilder */ $transactionBuilder = $objectManager->create(TransactionBuilder::class); $transactionAuthorize = $transactionBuilder->setPayment($payment) diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php index 6e06d749f3906..0206ecd6b876b 100644 --- a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php @@ -10,7 +10,10 @@ use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\ResourceModel\Order\Creditmemo\CollectionFactory as CreditmemoCollectionFactory; class RefundSettledCommandTest extends AbstractTest { @@ -29,6 +32,7 @@ public function testRefundSettledCommand() $order = $this->getOrderWithIncrementId('100000001'); $payment = $order->getPayment(); + $payment->setCreditmemo($this->getCreditmemo($order)); $paymentDO = $this->paymentFactory->create($payment); @@ -41,13 +45,35 @@ public function testRefundSettledCommand() $this->responseMock->method('getBody') ->willReturn(json_encode($response)); - $command->execute([ - 'payment' => $paymentDO, - 'amount' => 100.00 - ]); + $command->execute( + [ + 'payment' => $paymentDO, + 'amount' => 100.00 + ] + ); /** @var Payment $payment */ $this->assertTrue($payment->getIsTransactionClosed()); $this->assertSame('5678', $payment->getTransactionId()); } + + /** + * Retrieve creditmemo from order. + * + * @param Order $order + * @return CreditmemoInterface + */ + private function getCreditmemo(Order $order): CreditmemoInterface + { + /** @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo\Collection $creditMemoCollection */ + $creditMemoCollection = $this->objectManager->create(CreditmemoCollectionFactory::class)->create(); + + /** @var CreditmemoInterface $creditMemo */ + $creditMemo = $creditMemoCollection + ->setOrderFilter($order) + ->setPageSize(1) + ->getFirstItem(); + + return $creditMemo; + } } diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php index cac7c38971ae5..420d0f55cf34e 100644 --- a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php @@ -41,12 +41,14 @@ ->setMetaDescription('meta description') ->setVisibility(Visibility::VISIBILITY_BOTH) ->setStatus(Status::STATUS_ENABLED) - ->setStockData([ - 'use_config_manage_stock' => 1, - 'qty' => 100, - 'is_qty_decimal' => 0, - 'is_in_stock' => 1, - ])->setCanSaveCustomOptions(true) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ] + )->setCanSaveCustomOptions(true) ->setHasOptions(false); /** @var ProductRepositoryInterface $productRepository */ @@ -65,6 +67,7 @@ ->setLastname('Doe') ->setShippingMethod('flatrate_flatrate'); +/** @var Payment $payment */ $payment = $objectManager->create(Payment::class); $payment->setAdditionalInformation('ccLast4', '1111'); $payment->setAdditionalInformation('opaqueDataDescriptor', 'mydescriptor'); @@ -116,6 +119,7 @@ ->setShippingAddress($shippingAddress) ->setShippingDescription('Flat Rate - Fixed') ->setShippingAmount(10) + ->setBaseShippingAmount(10) ->setStoreId(1) ->addItem($orderItem1) ->addItem($orderItem2) From 98c2cb71012a7a7eea8119a6b8e498c8d81fe1e6 Mon Sep 17 00:00:00 2001 From: Rus0 <andonidominguez@wolfsellers.com> Date: Fri, 23 Aug 2019 14:39:18 -0500 Subject: [PATCH 383/841] Change to Different Class for reuse --- .../Model/Category/GetRootCategoryId.php | 40 +++++++++++++++++++ .../Model/Resolver/CategoryTree.php | 39 +++++------------- 2 files changed, 49 insertions(+), 30 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php new file mode 100644 index 0000000000000..deb002afda2f7 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php @@ -0,0 +1,40 @@ +<?php + +namespace Magento\CatalogGraphQl\Model\Category; + +class GetRootCategoryId +{ + + /** + * @var int + */ + private $rootCategoryId = null; + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * GetRootCategoryId constructor. + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + */ + public function __construct( + \Magento\Store\Model\StoreManagerInterface $storeManager + ) { + $this->storeManager = $storeManager; + } + + /** + * Get Root Category Id + * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function execute() + { + if ($this->rootCategoryId == null) { + $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); + } + + return $this->rootCategoryId; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 65534572ac07a..516bb576431da 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -10,11 +10,10 @@ use Magento\Catalog\Model\Category; use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; /** * Category tree field resolver, used for GraphQL request processing. @@ -40,33 +39,27 @@ class CategoryTree implements ResolverInterface * @var CheckCategoryIsActive */ private $checkCategoryIsActive; - - /** - * @var \Magento\Store\Model\StoreManagerInterface - */ - private $storeManager; - /** - * @var int + * @var \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId */ - private $rootCategoryId = null; + private $getRootCategoryId; /** - * @param \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree + * @param Products\DataProvider\CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CheckCategoryIsActive $checkCategoryIsActive - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId $getRootCategoryId */ public function __construct( Products\DataProvider\CategoryTree $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, CheckCategoryIsActive $checkCategoryIsActive, - \Magento\Store\Model\StoreManagerInterface $storeManager + \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId $getRootCategoryId ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; $this->checkCategoryIsActive = $checkCategoryIsActive; - $this->storeManager = $storeManager; + $this->getRootCategoryId = $getRootCategoryId; } /** @@ -78,7 +71,7 @@ public function __construct( */ private function getCategoryId(array $args) : int { - return isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId(); + return isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId->execute(); } /** @@ -91,7 +84,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $rootCategoryId = $this->getCategoryId($args); - if ($rootCategoryId !== $this->getRootCategoryId() && $rootCategoryId > 0) { + if ($rootCategoryId !== $this->getRootCategoryId->execute() && $rootCategoryId > 0) { $this->checkCategoryIsActive->execute($rootCategoryId); } $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); @@ -103,18 +96,4 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $result = $this->extractDataFromCategoryTree->execute($categoriesTree); return current($result); } - - /** - * Get Root Category Id - * @return int - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - public function getRootCategoryId() - { - if ($this->rootCategoryId == null) { - $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); - } - - return $this->rootCategoryId; - } } From 706f3c970a538705c8920fd347c576e162be0805 Mon Sep 17 00:00:00 2001 From: Rus0 <andonidominguez@wolfsellers.com> Date: Fri, 23 Aug 2019 14:39:18 -0500 Subject: [PATCH 384/841] Change to Different Class for reuse --- .../Model/Category/GetRootCategoryId.php | 40 +++++++++++++++++++ .../Model/Resolver/CategoryTree.php | 39 +++++------------- 2 files changed, 49 insertions(+), 30 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php new file mode 100644 index 0000000000000..deb002afda2f7 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php @@ -0,0 +1,40 @@ +<?php + +namespace Magento\CatalogGraphQl\Model\Category; + +class GetRootCategoryId +{ + + /** + * @var int + */ + private $rootCategoryId = null; + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * GetRootCategoryId constructor. + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + */ + public function __construct( + \Magento\Store\Model\StoreManagerInterface $storeManager + ) { + $this->storeManager = $storeManager; + } + + /** + * Get Root Category Id + * @return int + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function execute() + { + if ($this->rootCategoryId == null) { + $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); + } + + return $this->rootCategoryId; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 65534572ac07a..516bb576431da 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -10,11 +10,10 @@ use Magento\Catalog\Model\Category; use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; /** * Category tree field resolver, used for GraphQL request processing. @@ -40,33 +39,27 @@ class CategoryTree implements ResolverInterface * @var CheckCategoryIsActive */ private $checkCategoryIsActive; - - /** - * @var \Magento\Store\Model\StoreManagerInterface - */ - private $storeManager; - /** - * @var int + * @var \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId */ - private $rootCategoryId = null; + private $getRootCategoryId; /** - * @param \Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree $categoryTree + * @param Products\DataProvider\CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CheckCategoryIsActive $checkCategoryIsActive - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId $getRootCategoryId */ public function __construct( Products\DataProvider\CategoryTree $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, CheckCategoryIsActive $checkCategoryIsActive, - \Magento\Store\Model\StoreManagerInterface $storeManager + \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId $getRootCategoryId ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; $this->checkCategoryIsActive = $checkCategoryIsActive; - $this->storeManager = $storeManager; + $this->getRootCategoryId = $getRootCategoryId; } /** @@ -78,7 +71,7 @@ public function __construct( */ private function getCategoryId(array $args) : int { - return isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId(); + return isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId->execute(); } /** @@ -91,7 +84,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $rootCategoryId = $this->getCategoryId($args); - if ($rootCategoryId !== $this->getRootCategoryId() && $rootCategoryId > 0) { + if ($rootCategoryId !== $this->getRootCategoryId->execute() && $rootCategoryId > 0) { $this->checkCategoryIsActive->execute($rootCategoryId); } $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); @@ -103,18 +96,4 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $result = $this->extractDataFromCategoryTree->execute($categoriesTree); return current($result); } - - /** - * Get Root Category Id - * @return int - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - public function getRootCategoryId() - { - if ($this->rootCategoryId == null) { - $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); - } - - return $this->rootCategoryId; - } } From da0446ee60f2db9bf5d984df90901cd52a283e19 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Fri, 23 Aug 2019 15:23:45 -0500 Subject: [PATCH 385/841] Removing unnecessary variables and adding the categoryRoot model --- .../Model/Resolver/CategoryRoot.php | 61 +++---------------- 1 file changed, 8 insertions(+), 53 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php index a906a0ff14f70..1e0271c9fc788 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php @@ -7,14 +7,10 @@ namespace Magento\CatalogGraphQl\Model\Resolver; -use Magento\Catalog\Model\Category; -use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; -use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CatalogGraphQl\Model\Category\GetRootCategoryId; use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlInputException; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; /** * Category tree field resolver, used for GraphQL request processing. @@ -22,65 +18,24 @@ class CategoryRoot implements ResolverInterface { /** - * Name of type in GraphQL + * @var GetRootCategoryId */ - const CATEGORY_INTERFACE = 'CategoryInterface'; + private $getRootCategoryId; /** - * @var Products\DataProvider\CategoryTree - */ - private $categoryTree; - - /** - * @var ExtractDataFromCategoryTree - */ - private $extractDataFromCategoryTree; - - /** - * @var CheckCategoryIsActive - */ - private $checkCategoryIsActive; - - /** - * @var \Magento\Store\Model\StoreManagerInterface - */ - private $storeManager; - - /** - * @var int - */ - private $rootCategoryId = null; - - /** - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param GetRootCategoryId $getRootCategoryId */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager + GetRootCategoryId $getRootCategoryId ) { - $this->storeManager = $storeManager; + $this->getRootCategoryId = $getRootCategoryId; } - /** * @inheritdoc */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { - - return $this->getRootCategoryId(); - } - - /** - * Get Root Category Id - * @return int - * @throws \Magento\Framework\Exception\NoSuchEntityException - */ - public function getRootCategoryId() - { - if ($this->rootCategoryId == null) { - $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); - } - - return $this->rootCategoryId; + return $this->getRootCategoryId->execute(); } } From d8c85741ac1f30015d24ba007bfeacd78b9a85de Mon Sep 17 00:00:00 2001 From: Will Beltran <wbeltran@wolfsellers.com> Date: Fri, 23 Aug 2019 15:36:15 -0500 Subject: [PATCH 386/841] Solved Multiple declaration variables --- .../view/frontend/web/js/view/shipping-information.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js index a7e27aa3e4ee7..4e3033e5e51a9 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js @@ -28,8 +28,8 @@ define([ * @return {String} */ getShippingMethodTitle: function () { - var shippingMethod = quote.shippingMethod(); - var shippingMethodTitle = ''; + var shippingMethod = quote.shippingMethod(), + shippingMethodTitle = ''; if (typeof shippingMethod['method_title'] !== 'undefined') { shippingMethodTitle = ' - ' + shippingMethod['method_title']; From e3ae7c2ed20dff8a915ec8dd7e9e9cbe1518b3bf Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Fri, 23 Aug 2019 16:05:05 -0500 Subject: [PATCH 387/841] Comments --- app/code/Magento/BundleSampleData | 1 + .../Model/Category/GetRootCategoryId.php | 26 ++++++++++++++----- .../Model/Resolver/CategoryRoot.php | 10 ++++++- .../Model/Resolver/CategoryTree.php | 11 ++++++-- app/code/Magento/CatalogRuleSampleData | 1 + app/code/Magento/CatalogSampleData | 1 + app/code/Magento/CmsSampleData | 1 + app/code/Magento/ConfigurableSampleData | 1 + app/code/Magento/CustomerSampleData | 1 + app/code/Magento/DownloadableSampleData | 1 + app/code/Magento/GroupedProductSampleData | 1 + app/code/Magento/MsrpSampleData | 1 + app/code/Magento/OfflineShippingSampleData | 1 + app/code/Magento/ProductLinksSampleData | 1 + app/code/Magento/ReviewSampleData | 1 + app/code/Magento/SalesRuleSampleData | 1 + app/code/Magento/SalesSampleData | 1 + app/code/Magento/SwatchesSampleData | 1 + app/code/Magento/TaxSampleData | 1 + app/code/Magento/ThemeSampleData | 1 + app/code/Magento/WidgetSampleData | 1 + app/code/Magento/WishlistSampleData | 1 + 22 files changed, 56 insertions(+), 10 deletions(-) create mode 120000 app/code/Magento/BundleSampleData create mode 120000 app/code/Magento/CatalogRuleSampleData create mode 120000 app/code/Magento/CatalogSampleData create mode 120000 app/code/Magento/CmsSampleData create mode 120000 app/code/Magento/ConfigurableSampleData create mode 120000 app/code/Magento/CustomerSampleData create mode 120000 app/code/Magento/DownloadableSampleData create mode 120000 app/code/Magento/GroupedProductSampleData create mode 120000 app/code/Magento/MsrpSampleData create mode 120000 app/code/Magento/OfflineShippingSampleData create mode 120000 app/code/Magento/ProductLinksSampleData create mode 120000 app/code/Magento/ReviewSampleData create mode 120000 app/code/Magento/SalesRuleSampleData create mode 120000 app/code/Magento/SalesSampleData create mode 120000 app/code/Magento/SwatchesSampleData create mode 120000 app/code/Magento/TaxSampleData create mode 120000 app/code/Magento/ThemeSampleData create mode 120000 app/code/Magento/WidgetSampleData create mode 120000 app/code/Magento/WishlistSampleData diff --git a/app/code/Magento/BundleSampleData b/app/code/Magento/BundleSampleData new file mode 120000 index 0000000000000..7e35e78f27e91 --- /dev/null +++ b/app/code/Magento/BundleSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/BundleSampleData \ No newline at end of file diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php index deb002afda2f7..c8b80622433cb 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php @@ -1,25 +1,33 @@ <?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); namespace Magento\CatalogGraphQl\Model\Category; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; + class GetRootCategoryId { - /** * @var int */ - private $rootCategoryId = null; + private $rootCategoryId; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ private $storeManager; /** * GetRootCategoryId constructor. - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param StoreManagerInterface $storeManager */ public function __construct( - \Magento\Store\Model\StoreManagerInterface $storeManager + StoreManagerInterface $storeManager ) { $this->storeManager = $storeManager; } @@ -27,12 +35,16 @@ public function __construct( /** * Get Root Category Id * @return int - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws LocalizedException */ public function execute() { if ($this->rootCategoryId == null) { - $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); + try { + $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); + } catch (NoSuchEntityException $noSuchEntityException) { + throw new LocalizedException(__("Store does not exist.")); + } } return $this->rootCategoryId; diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php index 1e0271c9fc788..9720a6d6244d5 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php @@ -8,7 +8,9 @@ namespace Magento\CatalogGraphQl\Model\Resolver; use Magento\CatalogGraphQl\Model\Category\GetRootCategoryId; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; @@ -36,6 +38,12 @@ public function __construct( */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { - return $this->getRootCategoryId->execute(); + $rootCategoryId = 0; + try { + $rootCategoryId = $this->getRootCategoryId->execute(); + } catch (LocalizedException $exception) { + throw new GraphQlNoSuchEntityException(__('Store doesn\'t exist')); + } + return $rootCategoryId; } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 516bb576431da..f99e80eea493c 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -10,6 +10,7 @@ use Magento\Catalog\Model\Category; use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; @@ -67,11 +68,17 @@ public function __construct( * * @param array $args * @return int - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws GraphQlNoSuchEntityException */ private function getCategoryId(array $args) : int { - return isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId->execute(); + $rootCategoryId = 0; + try { + $rootCategoryId = isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId->execute(); + } catch (LocalizedException $exception) { + throw new GraphQlNoSuchEntityException(__('Store doesn\'t exist')); + } + return $rootCategoryId; } /** diff --git a/app/code/Magento/CatalogRuleSampleData b/app/code/Magento/CatalogRuleSampleData new file mode 120000 index 0000000000000..688c91d844c6a --- /dev/null +++ b/app/code/Magento/CatalogRuleSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/CatalogRuleSampleData \ No newline at end of file diff --git a/app/code/Magento/CatalogSampleData b/app/code/Magento/CatalogSampleData new file mode 120000 index 0000000000000..f8e606a9034ec --- /dev/null +++ b/app/code/Magento/CatalogSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/CatalogSampleData \ No newline at end of file diff --git a/app/code/Magento/CmsSampleData b/app/code/Magento/CmsSampleData new file mode 120000 index 0000000000000..ca842cd07d207 --- /dev/null +++ b/app/code/Magento/CmsSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/CmsSampleData \ No newline at end of file diff --git a/app/code/Magento/ConfigurableSampleData b/app/code/Magento/ConfigurableSampleData new file mode 120000 index 0000000000000..7ca081235faeb --- /dev/null +++ b/app/code/Magento/ConfigurableSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/ConfigurableSampleData \ No newline at end of file diff --git a/app/code/Magento/CustomerSampleData b/app/code/Magento/CustomerSampleData new file mode 120000 index 0000000000000..9c5c51b432d54 --- /dev/null +++ b/app/code/Magento/CustomerSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/CustomerSampleData \ No newline at end of file diff --git a/app/code/Magento/DownloadableSampleData b/app/code/Magento/DownloadableSampleData new file mode 120000 index 0000000000000..c13ec39b755be --- /dev/null +++ b/app/code/Magento/DownloadableSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/DownloadableSampleData \ No newline at end of file diff --git a/app/code/Magento/GroupedProductSampleData b/app/code/Magento/GroupedProductSampleData new file mode 120000 index 0000000000000..d2d359906cd7f --- /dev/null +++ b/app/code/Magento/GroupedProductSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/GroupedProductSampleData \ No newline at end of file diff --git a/app/code/Magento/MsrpSampleData b/app/code/Magento/MsrpSampleData new file mode 120000 index 0000000000000..bb6290ac49197 --- /dev/null +++ b/app/code/Magento/MsrpSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/MsrpSampleData \ No newline at end of file diff --git a/app/code/Magento/OfflineShippingSampleData b/app/code/Magento/OfflineShippingSampleData new file mode 120000 index 0000000000000..9156b62ece0da --- /dev/null +++ b/app/code/Magento/OfflineShippingSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/OfflineShippingSampleData \ No newline at end of file diff --git a/app/code/Magento/ProductLinksSampleData b/app/code/Magento/ProductLinksSampleData new file mode 120000 index 0000000000000..e4a134517d611 --- /dev/null +++ b/app/code/Magento/ProductLinksSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/ProductLinksSampleData \ No newline at end of file diff --git a/app/code/Magento/ReviewSampleData b/app/code/Magento/ReviewSampleData new file mode 120000 index 0000000000000..cbf588c39b8d7 --- /dev/null +++ b/app/code/Magento/ReviewSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/ReviewSampleData \ No newline at end of file diff --git a/app/code/Magento/SalesRuleSampleData b/app/code/Magento/SalesRuleSampleData new file mode 120000 index 0000000000000..ac7ac93163887 --- /dev/null +++ b/app/code/Magento/SalesRuleSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/SalesRuleSampleData \ No newline at end of file diff --git a/app/code/Magento/SalesSampleData b/app/code/Magento/SalesSampleData new file mode 120000 index 0000000000000..452e488f7c099 --- /dev/null +++ b/app/code/Magento/SalesSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/SalesSampleData \ No newline at end of file diff --git a/app/code/Magento/SwatchesSampleData b/app/code/Magento/SwatchesSampleData new file mode 120000 index 0000000000000..785ec021e4a26 --- /dev/null +++ b/app/code/Magento/SwatchesSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/SwatchesSampleData \ No newline at end of file diff --git a/app/code/Magento/TaxSampleData b/app/code/Magento/TaxSampleData new file mode 120000 index 0000000000000..ab74032bbf626 --- /dev/null +++ b/app/code/Magento/TaxSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/TaxSampleData \ No newline at end of file diff --git a/app/code/Magento/ThemeSampleData b/app/code/Magento/ThemeSampleData new file mode 120000 index 0000000000000..3d79314969a0f --- /dev/null +++ b/app/code/Magento/ThemeSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/ThemeSampleData \ No newline at end of file diff --git a/app/code/Magento/WidgetSampleData b/app/code/Magento/WidgetSampleData new file mode 120000 index 0000000000000..19d0ceb6d688e --- /dev/null +++ b/app/code/Magento/WidgetSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/WidgetSampleData \ No newline at end of file diff --git a/app/code/Magento/WishlistSampleData b/app/code/Magento/WishlistSampleData new file mode 120000 index 0000000000000..c2e787fc3e452 --- /dev/null +++ b/app/code/Magento/WishlistSampleData @@ -0,0 +1 @@ +/var/www/html/magento2-sample-data/app/code/Magento/WishlistSampleData \ No newline at end of file From 86228e9b4af4fd1c13e416b20f2a8280e0190c1c Mon Sep 17 00:00:00 2001 From: Will Beltran <wbeltran@wolfsellers.com> Date: Fri, 23 Aug 2019 16:06:00 -0500 Subject: [PATCH 388/841] Solved limit exceeds the maximum line 120 --- .../view/frontend/web/js/view/shipping-information.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js index 4e3033e5e51a9..2158873842687 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js @@ -35,7 +35,9 @@ define([ shippingMethodTitle = ' - ' + shippingMethod['method_title']; } - return shippingMethod ? shippingMethod['carrier_title'] + shippingMethodTitle : shippingMethod['carrier_title']; + return shippingMethod ? + shippingMethod['carrier_title'] + shippingMethodTitle : + shippingMethod['carrier_title']; }, /** From aa45743f0bc17ab763e4ce4aa7000db374ac39ef Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Fri, 23 Aug 2019 16:06:56 -0500 Subject: [PATCH 389/841] amend --- app/code/Magento/BundleSampleData | 1 - app/code/Magento/CatalogRuleSampleData | 1 - app/code/Magento/CatalogSampleData | 1 - app/code/Magento/CmsSampleData | 1 - app/code/Magento/ConfigurableSampleData | 1 - app/code/Magento/CustomerSampleData | 1 - app/code/Magento/DownloadableSampleData | 1 - app/code/Magento/GroupedProductSampleData | 1 - app/code/Magento/MsrpSampleData | 1 - app/code/Magento/OfflineShippingSampleData | 1 - app/code/Magento/ProductLinksSampleData | 1 - app/code/Magento/ReviewSampleData | 1 - app/code/Magento/SalesRuleSampleData | 1 - app/code/Magento/SalesSampleData | 1 - app/code/Magento/SwatchesSampleData | 1 - app/code/Magento/TaxSampleData | 1 - app/code/Magento/ThemeSampleData | 1 - app/code/Magento/WidgetSampleData | 1 - app/code/Magento/WishlistSampleData | 1 - 19 files changed, 19 deletions(-) delete mode 120000 app/code/Magento/BundleSampleData delete mode 120000 app/code/Magento/CatalogRuleSampleData delete mode 120000 app/code/Magento/CatalogSampleData delete mode 120000 app/code/Magento/CmsSampleData delete mode 120000 app/code/Magento/ConfigurableSampleData delete mode 120000 app/code/Magento/CustomerSampleData delete mode 120000 app/code/Magento/DownloadableSampleData delete mode 120000 app/code/Magento/GroupedProductSampleData delete mode 120000 app/code/Magento/MsrpSampleData delete mode 120000 app/code/Magento/OfflineShippingSampleData delete mode 120000 app/code/Magento/ProductLinksSampleData delete mode 120000 app/code/Magento/ReviewSampleData delete mode 120000 app/code/Magento/SalesRuleSampleData delete mode 120000 app/code/Magento/SalesSampleData delete mode 120000 app/code/Magento/SwatchesSampleData delete mode 120000 app/code/Magento/TaxSampleData delete mode 120000 app/code/Magento/ThemeSampleData delete mode 120000 app/code/Magento/WidgetSampleData delete mode 120000 app/code/Magento/WishlistSampleData diff --git a/app/code/Magento/BundleSampleData b/app/code/Magento/BundleSampleData deleted file mode 120000 index 7e35e78f27e91..0000000000000 --- a/app/code/Magento/BundleSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/BundleSampleData \ No newline at end of file diff --git a/app/code/Magento/CatalogRuleSampleData b/app/code/Magento/CatalogRuleSampleData deleted file mode 120000 index 688c91d844c6a..0000000000000 --- a/app/code/Magento/CatalogRuleSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/CatalogRuleSampleData \ No newline at end of file diff --git a/app/code/Magento/CatalogSampleData b/app/code/Magento/CatalogSampleData deleted file mode 120000 index f8e606a9034ec..0000000000000 --- a/app/code/Magento/CatalogSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/CatalogSampleData \ No newline at end of file diff --git a/app/code/Magento/CmsSampleData b/app/code/Magento/CmsSampleData deleted file mode 120000 index ca842cd07d207..0000000000000 --- a/app/code/Magento/CmsSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/CmsSampleData \ No newline at end of file diff --git a/app/code/Magento/ConfigurableSampleData b/app/code/Magento/ConfigurableSampleData deleted file mode 120000 index 7ca081235faeb..0000000000000 --- a/app/code/Magento/ConfigurableSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/ConfigurableSampleData \ No newline at end of file diff --git a/app/code/Magento/CustomerSampleData b/app/code/Magento/CustomerSampleData deleted file mode 120000 index 9c5c51b432d54..0000000000000 --- a/app/code/Magento/CustomerSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/CustomerSampleData \ No newline at end of file diff --git a/app/code/Magento/DownloadableSampleData b/app/code/Magento/DownloadableSampleData deleted file mode 120000 index c13ec39b755be..0000000000000 --- a/app/code/Magento/DownloadableSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/DownloadableSampleData \ No newline at end of file diff --git a/app/code/Magento/GroupedProductSampleData b/app/code/Magento/GroupedProductSampleData deleted file mode 120000 index d2d359906cd7f..0000000000000 --- a/app/code/Magento/GroupedProductSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/GroupedProductSampleData \ No newline at end of file diff --git a/app/code/Magento/MsrpSampleData b/app/code/Magento/MsrpSampleData deleted file mode 120000 index bb6290ac49197..0000000000000 --- a/app/code/Magento/MsrpSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/MsrpSampleData \ No newline at end of file diff --git a/app/code/Magento/OfflineShippingSampleData b/app/code/Magento/OfflineShippingSampleData deleted file mode 120000 index 9156b62ece0da..0000000000000 --- a/app/code/Magento/OfflineShippingSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/OfflineShippingSampleData \ No newline at end of file diff --git a/app/code/Magento/ProductLinksSampleData b/app/code/Magento/ProductLinksSampleData deleted file mode 120000 index e4a134517d611..0000000000000 --- a/app/code/Magento/ProductLinksSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/ProductLinksSampleData \ No newline at end of file diff --git a/app/code/Magento/ReviewSampleData b/app/code/Magento/ReviewSampleData deleted file mode 120000 index cbf588c39b8d7..0000000000000 --- a/app/code/Magento/ReviewSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/ReviewSampleData \ No newline at end of file diff --git a/app/code/Magento/SalesRuleSampleData b/app/code/Magento/SalesRuleSampleData deleted file mode 120000 index ac7ac93163887..0000000000000 --- a/app/code/Magento/SalesRuleSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/SalesRuleSampleData \ No newline at end of file diff --git a/app/code/Magento/SalesSampleData b/app/code/Magento/SalesSampleData deleted file mode 120000 index 452e488f7c099..0000000000000 --- a/app/code/Magento/SalesSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/SalesSampleData \ No newline at end of file diff --git a/app/code/Magento/SwatchesSampleData b/app/code/Magento/SwatchesSampleData deleted file mode 120000 index 785ec021e4a26..0000000000000 --- a/app/code/Magento/SwatchesSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/SwatchesSampleData \ No newline at end of file diff --git a/app/code/Magento/TaxSampleData b/app/code/Magento/TaxSampleData deleted file mode 120000 index ab74032bbf626..0000000000000 --- a/app/code/Magento/TaxSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/TaxSampleData \ No newline at end of file diff --git a/app/code/Magento/ThemeSampleData b/app/code/Magento/ThemeSampleData deleted file mode 120000 index 3d79314969a0f..0000000000000 --- a/app/code/Magento/ThemeSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/ThemeSampleData \ No newline at end of file diff --git a/app/code/Magento/WidgetSampleData b/app/code/Magento/WidgetSampleData deleted file mode 120000 index 19d0ceb6d688e..0000000000000 --- a/app/code/Magento/WidgetSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/WidgetSampleData \ No newline at end of file diff --git a/app/code/Magento/WishlistSampleData b/app/code/Magento/WishlistSampleData deleted file mode 120000 index c2e787fc3e452..0000000000000 --- a/app/code/Magento/WishlistSampleData +++ /dev/null @@ -1 +0,0 @@ -/var/www/html/magento2-sample-data/app/code/Magento/WishlistSampleData \ No newline at end of file From dbc514c634cf4d447291e2cf7cf2f83d112e9480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torben=20Ho=CC=88hn?= <torhoehn@gmail.com> Date: Fri, 23 Aug 2019 23:08:24 +0200 Subject: [PATCH 390/841] add config for disabling swatch tooltips --- .../Block/Product/Renderer/Configurable.php | 82 +++++++++++++------ .../Magento/Swatches/etc/adminhtml/system.xml | 4 + app/code/Magento/Swatches/etc/config.xml | 1 + .../templates/product/listing/renderer.phtml | 5 +- .../templates/product/view/renderer.phtml | 3 +- .../view/frontend/web/js/swatch-renderer.js | 13 +-- 6 files changed, 74 insertions(+), 34 deletions(-) diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php index 0848f566f67bb..35bf199681720 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types = 1); namespace Magento\Swatches\Block\Product\Renderer; use Magento\Catalog\Block\Product\Context; @@ -57,6 +58,16 @@ class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\ */ const SWATCH_THUMBNAIL_NAME = 'swatchThumb'; + /** + * Config path which contains number of swatches per product + */ + const XML_PATH_SWATCHES_PER_PRODUCT = 'catalog/frontend/swatches_per_product'; + + /** + * Config path if swatch tooltips are enabled + */ + const XML_PATH_SHOW_SWATCH_TOOLTIP = 'catalog/frontend/show_swatch_tooltip'; + /** * @var Product */ @@ -93,19 +104,19 @@ class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\ /** * @SuppressWarnings(PHPMD.ExcessiveParameterList) - * @param Context $context - * @param ArrayUtils $arrayUtils - * @param EncoderInterface $jsonEncoder - * @param Data $helper - * @param CatalogProduct $catalogProduct - * @param CurrentCustomer $currentCustomer - * @param PriceCurrencyInterface $priceCurrency - * @param ConfigurableAttributeData $configurableAttributeData - * @param SwatchData $swatchHelper - * @param Media $swatchMediaHelper - * @param array $data + * @param Context $context + * @param ArrayUtils $arrayUtils + * @param EncoderInterface $jsonEncoder + * @param Data $helper + * @param CatalogProduct $catalogProduct + * @param CurrentCustomer $currentCustomer + * @param PriceCurrencyInterface $priceCurrency + * @param ConfigurableAttributeData $configurableAttributeData + * @param SwatchData $swatchHelper + * @param Media $swatchMediaHelper + * @param array $data * @param SwatchAttributesProvider|null $swatchAttributesProvider - * @param UrlBuilder|null $imageUrlBuilder + * @param UrlBuilder|null $imageUrlBuilder */ public function __construct( Context $context, @@ -172,7 +183,6 @@ public function getJsonSwatchConfig() $attributesData = $this->getSwatchAttributesData(); $allOptionIds = $this->getConfigurableOptionsIds($attributesData); $swatchesData = $this->swatchHelper->getSwatchesByOptionsId($allOptionIds); - $config = []; foreach ($attributesData as $attributeId => $attributeDataArray) { if (isset($attributeDataArray['options'])) { @@ -200,7 +210,20 @@ public function getJsonSwatchConfig() public function getNumberSwatchesPerProduct() { return $this->_scopeConfig->getValue( - 'catalog/frontend/swatches_per_product', + self::XML_PATH_SWATCHES_PER_PRODUCT, + ScopeInterface::SCOPE_STORE + ); + } + + /** + * Get config if swatch tooltips should be rendered. + * + * @return string + */ + public function getShowSwatchTooltip() + { + return $this->_scopeConfig->getValue( + self::XML_PATH_SHOW_SWATCH_TOOLTIP, ScopeInterface::SCOPE_STORE ); } @@ -209,11 +232,13 @@ public function getNumberSwatchesPerProduct() * Set product to block * * @param Product $product + * * @return $this */ public function setProduct(Product $product) { $this->product = $product; + return $this; } @@ -244,10 +269,10 @@ protected function getSwatchAttributesData() /** * Init isProductHasSwatchAttribute. * + * @return void * @deprecated 100.1.5 Method isProductHasSwatchAttribute() is used instead of this. * * @codeCoverageIgnore - * @return void */ protected function initIsProductHasSwatchAttribute() { @@ -263,6 +288,7 @@ protected function initIsProductHasSwatchAttribute() protected function isProductHasSwatchAttribute() { $swatchAttributes = $this->swatchAttributesProvider->provide($this->getProduct()); + return count($swatchAttributes) > 0; } @@ -272,6 +298,7 @@ protected function isProductHasSwatchAttribute() * @param array $options * @param array $swatchesCollectionArray * @param array $attributeDataArray + * * @return array */ protected function addSwatchDataForAttribute( @@ -294,9 +321,10 @@ protected function addSwatchDataForAttribute( /** * Add media from variation * - * @param array $swatch + * @param array $swatch * @param integer $optionId - * @param array $attributeDataArray + * @param array $attributeDataArray + * * @return array */ protected function addAdditionalMediaData(array $swatch, $optionId, array $attributeDataArray) @@ -305,11 +333,12 @@ protected function addAdditionalMediaData(array $swatch, $optionId, array $attri && $attributeDataArray['use_product_image_for_swatch'] ) { $variationMedia = $this->getVariationMedia($attributeDataArray['attribute_code'], $optionId); - if (! empty($variationMedia)) { + if (!empty($variationMedia)) { $swatch['type'] = Swatch::SWATCH_TYPE_VISUAL_IMAGE; $swatch = array_merge($swatch, $variationMedia); } } + return $swatch; } @@ -317,12 +346,12 @@ protected function addAdditionalMediaData(array $swatch, $optionId, array $attri * Retrieve Swatch data for config * * @param array $swatchDataArray + * * @return array */ protected function extractNecessarySwatchData(array $swatchDataArray) { $result['type'] = $swatchDataArray['type']; - if ($result['type'] == Swatch::SWATCH_TYPE_VISUAL_IMAGE && !empty($swatchDataArray['value'])) { $result['value'] = $this->swatchMediaHelper->getSwatchAttributeImage( Swatch::SWATCH_IMAGE_NAME, @@ -342,8 +371,9 @@ protected function extractNecessarySwatchData(array $swatchDataArray) /** * Generate Product Media array * - * @param string $attributeCode + * @param string $attributeCode * @param integer $optionId + * * @return array */ protected function getVariationMedia($attributeCode, $optionId) @@ -352,14 +382,12 @@ protected function getVariationMedia($attributeCode, $optionId) $this->getProduct(), [$attributeCode => $optionId] ); - if (!$variationProduct) { $variationProduct = $this->swatchHelper->loadFirstVariationWithImage( $this->getProduct(), [$attributeCode => $optionId] ); } - $variationMediaArray = []; if ($variationProduct) { $variationMediaArray = [ @@ -375,7 +403,8 @@ protected function getVariationMedia($attributeCode, $optionId) * Get swatch product image. * * @param Product $childProduct - * @param string $imageType + * @param string $imageType + * * @return string */ protected function getSwatchProductImage(Product $childProduct, $imageType) @@ -387,7 +416,6 @@ protected function getSwatchProductImage(Product $childProduct, $imageType) $swatchImageId = $imageType == Swatch::SWATCH_IMAGE_NAME ? 'swatch_image_base' : 'swatch_thumb_base'; $imageAttributes = ['type' => 'image']; } - if (!empty($swatchImageId) && !empty($imageAttributes['type'])) { return $this->imageUrlBuilder->getUrl($childProduct->getData($imageAttributes['type']), $swatchImageId); } @@ -397,7 +425,8 @@ protected function getSwatchProductImage(Product $childProduct, $imageType) * Check if product have image. * * @param Product $product - * @param string $imageType + * @param string $imageType + * * @return bool */ protected function isProductHasImage(Product $product, $imageType) @@ -409,6 +438,7 @@ protected function isProductHasImage(Product $product, $imageType) * Get configurable options ids. * * @param array $attributeData + * * @return array * @since 100.0.3 */ @@ -425,6 +455,7 @@ protected function getConfigurableOptionsIds(array $attributeData) } } } + return array_keys($ids); } @@ -509,7 +540,6 @@ public function getJsonSwatchSizeConfig() { $imageConfig = $this->swatchMediaHelper->getImageConfig(); $sizeConfig = []; - $sizeConfig[self::SWATCH_IMAGE_NAME]['width'] = $imageConfig[Swatch::SWATCH_IMAGE_NAME]['width']; $sizeConfig[self::SWATCH_IMAGE_NAME]['height'] = $imageConfig[Swatch::SWATCH_IMAGE_NAME]['height']; $sizeConfig[self::SWATCH_THUMBNAIL_NAME]['height'] = $imageConfig[Swatch::SWATCH_THUMBNAIL_NAME]['height']; diff --git a/app/code/Magento/Swatches/etc/adminhtml/system.xml b/app/code/Magento/Swatches/etc/adminhtml/system.xml index 2cf40ae83cc3b..6fbf110fadcd3 100644 --- a/app/code/Magento/Swatches/etc/adminhtml/system.xml +++ b/app/code/Magento/Swatches/etc/adminhtml/system.xml @@ -17,6 +17,10 @@ <label>Show Swatches in Product List</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> + <field id="show_swatch_tooltip" translate="label" type="select" sortOrder="320" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Show Swatch Tooltip</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> </group> </section> </system> diff --git a/app/code/Magento/Swatches/etc/config.xml b/app/code/Magento/Swatches/etc/config.xml index 65b36558c2796..4140acc4974d6 100644 --- a/app/code/Magento/Swatches/etc/config.xml +++ b/app/code/Magento/Swatches/etc/config.xml @@ -11,6 +11,7 @@ <frontend> <swatches_per_product>16</swatches_per_product> <show_swatches_in_product_list>1</show_swatches_in_product_list> + <show_swatch_tooltip>1</show_swatch_tooltip> </frontend> </catalog> <general> diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml index 777277a15d8cd..3c2f15dea0ab4 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml @@ -22,7 +22,8 @@ $productId = $block->getProduct()->getId(); "jsonConfig": <?= /* @noEscape */ $block->getJsonConfig() ?>, "jsonSwatchConfig": <?= /* @noEscape */ $block->getJsonSwatchConfig() ?>, "mediaCallback": "<?= $block->escapeJs($block->escapeUrl($block->getMediaCallback())) ?>", - "jsonSwatchImageSizeConfig": <?= /* @noEscape */ $block->getJsonSwatchSizeConfig() ?> + "jsonSwatchImageSizeConfig": <?= /* @noEscape */ $block->getJsonSwatchSizeConfig() ?>, + "showTooltip": <?= $block->escapeJs($block->getShowSwatchTooltip()) ?> } } } @@ -39,4 +40,4 @@ $productId = $block->getProduct()->getId(); } } } -</script> \ No newline at end of file +</script> diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml index c85a6908413b5..46666e880bb03 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml @@ -15,7 +15,8 @@ "jsonSwatchConfig": <?= /* @noEscape */ $swatchOptions = $block->getJsonSwatchConfig() ?>, "mediaCallback": "<?= $block->escapeJs($block->escapeUrl($block->getMediaCallback())) ?>", "gallerySwitchStrategy": "<?= $block->escapeJs($block->getVar('gallery_switch_strategy', 'Magento_ConfigurableProduct')) ?: 'replace'; ?>", - "jsonSwatchImageSizeConfig": <?= /* @noEscape */ $block->getJsonSwatchSizeConfig() ?> + "jsonSwatchImageSizeConfig": <?= /* @noEscape */ $block->getJsonSwatchSizeConfig() ?>, + "showTooltip": <?= $block->escapeJs($block->getShowSwatchTooltip()) ?> } }, "*" : { diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index d6302cff83bff..aa7d0a08ce697 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -385,7 +385,8 @@ define([ var $widget = this, container = this.element, classes = this.options.classes, - chooseText = this.options.jsonConfig.chooseText; + chooseText = this.options.jsonConfig.chooseText, + showTooltip = this.options.showTooltip; $widget.optionsMap = {}; @@ -452,10 +453,12 @@ define([ }); }); - // Connect Tooltip - container - .find('[option-type="1"], [option-type="2"], [option-type="0"], [option-type="3"]') - .SwatchRendererTooltip(); + if (showTooltip === 1) { + // Connect Tooltip + container + .find('[option-type="1"], [option-type="2"], [option-type="0"], [option-type="3"]') + .SwatchRendererTooltip(); + } // Hide all elements below more button $('.' + classes.moreButton).nextAll().hide(); From 9b2297afa71cbd38456fb8bc206e02a14d456f59 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Fri, 23 Aug 2019 21:28:15 -0400 Subject: [PATCH 391/841] update variable name --- .../Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php index 04b9354855ea9..dfb8b748469b8 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/ScalarTypes.php @@ -20,8 +20,8 @@ class ScalarTypes */ public function isScalarType(string $typeName) : bool { - $internalTypes = \GraphQL\Type\Definition\Type::getStandardTypes(); - return isset($internalTypes[$typeName]) ? true : false; + $standardTypes = \GraphQL\Type\Definition\Type::getStandardTypes(); + return isset($standardTypes[$typeName]) ? true : false; } /** @@ -33,9 +33,9 @@ public function isScalarType(string $typeName) : bool */ public function getScalarTypeInstance(string $typeName) : \GraphQL\Type\Definition\Type { - $internalTypes = \GraphQL\Type\Definition\Type::getStandardTypes(); + $standardTypes = \GraphQL\Type\Definition\Type::getStandardTypes(); if ($this->isScalarType($typeName)) { - return $internalTypes[$typeName]; + return $standardTypes[$typeName]; } else { throw new \LogicException(sprintf('Scalar type %s doesn\'t exist', $typeName)); } From 3ee407ae76ca30b337518cb8838f5ff594bf0152 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sat, 24 Aug 2019 21:50:35 +0700 Subject: [PATCH 392/841] Resolve undefined constant KEY_EXCHANGE_RATE in ExchangeRateInterface --- app/code/Magento/Directory/Model/Data/ExchangeRate.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Directory/Model/Data/ExchangeRate.php b/app/code/Magento/Directory/Model/Data/ExchangeRate.php index 35aee75f10481..1efd0eb7f8cd0 100644 --- a/app/code/Magento/Directory/Model/Data/ExchangeRate.php +++ b/app/code/Magento/Directory/Model/Data/ExchangeRate.php @@ -5,6 +5,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Directory\Model\Data; /** @@ -17,6 +20,7 @@ class ExchangeRate extends \Magento\Framework\Api\AbstractExtensibleObject imple { const KEY_CURRENCY_TO = 'currency_to'; const KEY_RATE = 'rate'; + const KEY_EXCHANGE_RATES = 'exchange_rates'; /** * @inheritDoc From c5966c4b19b6833e48daf51c605454c0d3b7876a Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sat, 24 Aug 2019 17:25:59 -0500 Subject: [PATCH 393/841] MC-17824: MAP and MSRP are being used interchangeably when they are different --- .../Patch/Data/ChangeMsrpAttributeLabel.php | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php diff --git a/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php b/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php new file mode 100644 index 0000000000000..547287066a30f --- /dev/null +++ b/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Msrp\Setup\Patch\Data; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Setup\CategorySetupFactory; +use Magento\Framework\Setup\Patch\DataPatchInterface; + +/** + * Change label for MSRP attribute. + */ +class ChangeMsrpAttributeLabel implements DataPatchInterface +{ + /** + * @var CategorySetupFactory + */ + private $categorySetupFactory; + + /** + * @param CategorySetupFactory $categorySetupFactory + */ + public function __construct(CategorySetupFactory $categorySetupFactory) + { + $this->categorySetupFactory = $categorySetupFactory; + } + + /** + * @inheritdoc + */ + public function apply() + { + /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + $categorySetup = $this->categorySetupFactory->create(); + $entityTypeId = $categorySetup->getEntityTypeId(Product::ENTITY); + $msrpAttribute = $categorySetup->getAttribute($entityTypeId, 'msrp'); + $categorySetup->updateAttribute( + $entityTypeId, + $msrpAttribute['attribute_id'], + 'frontend_label', + 'Minimum Advertised Price' + ); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return [ + InitializeMsrpAttributes::class, + ]; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} From 056ccf5a9110a35eaa4f3c5165ce9bd38d6ceda4 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 25 Aug 2019 06:53:04 +0100 Subject: [PATCH 394/841] Update Magento_AmqpStore ReadMe --- app/code/Magento/AmqpStore/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/AmqpStore/README.md b/app/code/Magento/AmqpStore/README.md index 0f84c8ff3276e..7ef84341f4419 100644 --- a/app/code/Magento/AmqpStore/README.md +++ b/app/code/Magento/AmqpStore/README.md @@ -1,3 +1,9 @@ -# Amqp Store +# Magento_AmqpStore module -**AmqpStore** provides ability to specify store before publish messages with Amqp. +Magento_AmqpStore provides ability to specify store before publish messages with with the Advanced Message Queuing Protocol (AMQP). + +## Extensibility + +Extension developers can interact with the Magento_AmqpStore module using plugins. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AmqpStore module. From ec8026be77d85d2f03afc1f2df8c8fbcea3c8d21 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 25 Aug 2019 07:30:31 +0100 Subject: [PATCH 395/841] Update Magento_AsynchronousOperations ReadMe --- .../Magento/AsynchronousOperations/README.md | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/AsynchronousOperations/README.md b/app/code/Magento/AsynchronousOperations/README.md index fb7d53df1b81c..f4678a0ebab22 100644 --- a/app/code/Magento/AsynchronousOperations/README.md +++ b/app/code/Magento/AsynchronousOperations/README.md @@ -1 +1,48 @@ - This component is designed to provide response for client who launched the bulk operation as soon as possible and postpone handling of operations moving them to background handler. \ No newline at end of file +# Magento_AsynchronousOperations module + +This component is designed to provide response for client who launched the bulk operation as soon as possible and postpone handling of operations moving them to background handler. + +## Installation details + +The Magento_AsynchronousOperations module creates the following tables in the database: + +- `magento_bulk` +- `magento_operation` +- `magento_acknowledged_bulk` + +Before disabling or uninstalling this module, note that the following modules depends on this module: + +- Magento_WebapiAsync + +For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html). + +## Extensibility + +Extension developers can interact with the Magento_AsynchronousOperations module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AsynchronousOperations module. + +### Layouts + +This module introduces the following layouts and layout handles in the `view/adminhtml/layout` directory: + +- `bulk_bulk_details` +- `bulk_bulk_details_modal` +- `bulk_index_index` + +For more information about layouts in Magento 2, see the [Layout documentation](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/layout-overview.html). + +### UI components + +You can extend Magento_AsynchronousOperations module using the following configuration files in `view/adminhtml/ui_component/` directory: + +- `bulk_details_form` +- `bulk_details_form_modal` +- `bulk_listing` +- `failed_operation_listing` +- `failed_operation_modal_listing` +- `notification_area` +- `retriable_operation_listing` +- `retriable_operation_modal_listing` + +For information about UI components in Magento 2, see [Overview of UI components](https://devdocs.magento.com/guides/v2.3/ui_comp_guide/bk-ui_comps.html). From 3e6104de227981da9631986aacb60a249fa063bc Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 25 Aug 2019 07:04:18 +0100 Subject: [PATCH 396/841] Update Magento_Analytics ReadMe --- app/code/Magento/Analytics/README.md | 42 +++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/app/code/Magento/Analytics/README.md b/app/code/Magento/Analytics/README.md index aa424182e2ebd..cb78a27dae8e0 100644 --- a/app/code/Magento/Analytics/README.md +++ b/app/code/Magento/Analytics/README.md @@ -1,18 +1,28 @@ -# Magento_Analytics Module +# Magento_Analytics module The Magento_Analytics module integrates your Magento instance with the [Magento Business Intelligence (MBI)](https://magento.com/products/business-intelligence) to use [Advanced Reporting](https://devdocs.magento.com/guides/v2.3/advanced-reporting/modules.html) functionality. The module implements the following functionality: -* enabling subscription to the MBI and automatic re-subscription -* changing the base URL with the same MBI account remained -* declaring the configuration schemas for report data collection -* collecting the Magento instance data as reports for the MBI -* introducing API that provides the collected data -* extending Magento configuration with the module parameters: - * subscription status (enabled/disabled) - * industry (a business area in which the instance website works) - * time of data collection (time of the day when the module collects data) +- Enabling subscription to the MBI and automatic re-subscription +- Changing the base URL with the same MBI account remained +- Declaring the configuration schemas for report data collection +- Collecting the Magento instance data as reports for the MBI +- Introducing API that provides the collected data +- Extending Magento configuration with the module parameters: + - Subscription status (enabled/disabled) + - Industry (a business area in which the instance website works) + - Time of data collection (time of the day when the module collects data) + +## Installation details + +Before disabling or uninstalling this module, note that the following modules depends on this module: +- Magento_CatalogAnalytics +- Magento_CustomerAnalytics +- Magento_QuoteAnalytics +- Magento_ReviewAnalytics +- Magento_SalesAnalytics +- Magento_WishlistAnalytics ## Structure @@ -29,12 +39,12 @@ The subscription to the MBI service is enabled during the installation process o Configuration settings for the Analytics module can be modified in the Admin Panel on the Stores > Configuration page under the General > Advanced Reporting tab. The following options can be adjusted: -* Advanced Reporting Service (Enabled/Disabled) - * Alters the status of the Advanced Reporting subscription -* Time of day to send data (Hour/Minute/Second in the store's time zone) - * Defines when the data collection process for the Advanced Reporting service occurs -* Industry - * Defines the industry of the store in order to create a personalized Advanced Reporting experience +- Advanced Reporting Service (Enabled/Disabled) + - Alters the status of the Advanced Reporting subscription +- Time of day to send data (Hour/Minute/Second in the store's time zone) + - Defines when the data collection process for the Advanced Reporting service occurs +- Industry + - Defines the industry of the store in order to create a personalized Advanced Reporting experience ## Extensibility From 418d274305a1b5c9c7c90f52245f93153653e404 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 25 Aug 2019 07:43:56 +0100 Subject: [PATCH 397/841] Update Magento_Authorization ReadMe --- app/code/Magento/Authorization/README.md | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Authorization/README.md b/app/code/Magento/Authorization/README.md index f74f5b6c70340..4b8afb4fef0d3 100644 --- a/app/code/Magento/Authorization/README.md +++ b/app/code/Magento/Authorization/README.md @@ -1,4 +1,20 @@ -# Authorization +# Magento_Authorization module -**Authorization** enables management of access control list roles and -rules in the application. +The Magento_Authorization module enables management of access control list roles and rules in the application. + +## Installation details + +The Magento_AdminNotification module creates the following tables in the database: + +- `authorization_role` +- `authorization_rule` + +Before disabling or uninstalling this module, note that the Magento_GraphQl module depends on this module. + +For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html). + +## Extensibility + +Extension developers can interact with the Magento_Authorization module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_Authorization module. From 8031a9c589799ef15d99b600e06797d51086244a Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 25 Aug 2019 08:09:49 +0100 Subject: [PATCH 398/841] Update Magento_Authorizenet ReadMe --- app/code/Magento/Authorizenet/README.md | 43 ++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Authorizenet/README.md b/app/code/Magento/Authorizenet/README.md index 380161d8b264e..ef022a6e236a2 100644 --- a/app/code/Magento/Authorizenet/README.md +++ b/app/code/Magento/Authorizenet/README.md @@ -1 +1,42 @@ -The Magento_Authorizenet module implements the integration with the Authorize.Net payment gateway and makes the latter available as a payment method in Magento. +# Magento_Authorizenet module + +The Magento_Authorizenet module is a part of the staging functionality in Magento EE. The module adds the “Configurations” tab and the configuration wizard to the Schedule Update form of a product. You can change the Configurable Product attributes in campaigns. These updates are shown on the campaign dashboard. + +## Extensibility + +Extension developers can interact with the Magento_Authorizenet module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_Authorizenet module. + +### Events + +This module dispatches the following events: + + - `checkout_directpost_placeOrder` event in the `\Magento\Authorizenet\Controller\Directpost\Payment\Place::placeCheckoutOrder()` method. Parameters: + - `result` is a data object (`\Magento\Framework\DataObject` class). + - `action` is a controller object (`\Magento\Authorizenet\Controller\Directpost\Payment\Place`). + + - `order_cancel_after` event in the `\Magento\Authorizenet\Model\Directpost::declineOrder()` method. Parameters: + - `order` is an order object (`\Magento\Sales\Model\Order` class). + + +This module observes the following events: + + - `checkout_submit_all_after` event in `Magento\Authorizenet\Observer\SaveOrderAfterSubmitObserver` file. + - `checkout_directpost_placeOrder` event in `Magento\Authorizenet\Observer\AddFieldsToResponseObserver` file. + +For information about an event in Magento 2, see [Events and observers](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). + +### Layouts + +This module introduces the following layouts and layout handles in the `view/adminhtml/layout` directory: + +- `adminhtml_authorizenet_directpost_payment_redirect` + +This module introduces the following layouts and layout handles in the `view/frontend/layout` directory: + +- `authorizenet_directpost_payment_backendresponse` +- `authorizenet_directpost_payment_redirect` +- `authorizenet_directpost_payment_response` + +For more information about layouts in Magento 2, see the [Layout documentation](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/layout-overview.html). From 9adda54fc2f7129afe05aa116b3a28943df389c7 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sun, 25 Aug 2019 02:37:04 -0500 Subject: [PATCH 399/841] MC-17824: MAP and MSRP are being used interchangeably when they are different --- .../AssertProductEditPageAdvancedPricingFields.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php b/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php index d1b56bcf5c0f8..2901ff86dd5e7 100644 --- a/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php +++ b/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php @@ -11,16 +11,16 @@ use Magento\Mtf\Fixture\FixtureInterface; /** - * Check "Manufacturer's Suggested Retail Price" field on "Advanced pricing" page. + * Check "Minimum Advertised Price" field on "Advanced pricing" page. */ class AssertProductEditPageAdvancedPricingFields extends AbstractConstraint { /** - * Title of "Manufacturer's Suggested Retail Price" field. + * Title of "Minimum Advertised Price" field. * * @var string */ - private $manufacturerFieldTitle = 'Manufacturer\'s Suggested Retail Price'; + private $manufacturerFieldTitle = 'Minimum Advertised Price'; /** * @param CatalogProductEdit $catalogProductEdit @@ -35,7 +35,7 @@ public function processAssert(CatalogProductEdit $catalogProductEdit, FixtureInt \PHPUnit\Framework\Assert::assertTrue( $advancedPricing->checkField($this->manufacturerFieldTitle), - '"Manufacturer\'s Suggested Retail Price" field is not correct.' + '"Minimum Advertised Price" field is not correct.' ); } @@ -46,6 +46,6 @@ public function processAssert(CatalogProductEdit $catalogProductEdit, FixtureInt */ public function toString() { - return '"Manufacturer\'s Suggested Retail Price" field is correct.'; + return '"Minimum Advertised Price" field is correct.'; } } From d610ae7bbedb995bfb7af4ea88814148150e94a0 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sun, 25 Aug 2019 17:26:44 +0700 Subject: [PATCH 400/841] Resolve undefined variable when call getPdf() --- app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php index 74f3eebf8fcb9..cea4086b6ba83 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php @@ -3,10 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Sales\Model\Order\Pdf; /** * Sales Order Creditmemo PDF model + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Creditmemo extends AbstractPdf @@ -182,9 +186,6 @@ public function getPdf($creditmemos = []) $this->insertTotals($page, $creditmemo); } $this->_afterGetPdf(); - if ($creditmemo->getStoreId()) { - $this->_localeResolver->revert(); - } return $pdf; } From 7d9b27d3bc8b2e08118c037a9c412bc8dde10ea2 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 26 Aug 2019 08:33:09 +0700 Subject: [PATCH 401/841] Resolve undefined variable in cleanFileExtensions() function --- .../Magento/Catalog/Model/Product/Option.php | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/Option.php b/app/code/Magento/Catalog/Model/Product/Option.php index b4a4ec08d390d..4f730834412e4 100644 --- a/app/code/Magento/Catalog/Model/Product/Option.php +++ b/app/code/Magento/Catalog/Model/Product/Option.php @@ -167,6 +167,8 @@ protected function _getResource() } /** + * Construct function + * * @return void */ protected function _construct() @@ -215,6 +217,8 @@ public function hasValues($type = null) } /** + * Get values + * * @return ProductCustomOptionValuesInterface[]|null */ public function getValues() @@ -345,7 +349,8 @@ public function groupFactory($type) } /** - * {@inheritdoc} + * @inheritdoc + * * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @since 101.0.0 */ @@ -396,6 +401,8 @@ public function beforeSave() } /** + * After save + * * @return \Magento\Framework\Model\AbstractModel * @throws \Magento\Framework\Exception\LocalizedException */ @@ -424,7 +431,8 @@ public function afterSave() /** * Return price. If $flag is true and price is percent - * return converted percent to price + * + * Return converted percent to price * * @param bool $flag * @return float @@ -555,7 +563,7 @@ protected function _clearReferences() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _getValidationRulesBeforeSave() { @@ -649,6 +657,8 @@ public function getSku() } /** + * Get file extension + * * @return string|null */ public function getFileExtension() @@ -657,6 +667,8 @@ public function getFileExtension() } /** + * Get Max Characters + * * @return int|null */ public function getMaxCharacters() @@ -665,6 +677,8 @@ public function getMaxCharacters() } /** + * Get image size X + * * @return int|null */ public function getImageSizeX() @@ -673,6 +687,8 @@ public function getImageSizeX() } /** + * Get image size Y + * * @return int|null */ public function getImageSizeY() @@ -780,6 +796,8 @@ public function setSku($sku) } /** + * Set File Extension + * * @param string $fileExtension * @return $this */ @@ -789,6 +807,8 @@ public function setFileExtension($fileExtension) } /** + * Set Max Characters + * * @param int $maxCharacters * @return $this */ @@ -798,6 +818,8 @@ public function setMaxCharacters($maxCharacters) } /** + * Set Image Size X + * * @param int $imageSizeX * @return $this */ @@ -807,6 +829,8 @@ public function setImageSizeX($imageSizeX) } /** + * Set Image Size Y + * * @param int $imageSizeY * @return $this */ @@ -816,6 +840,8 @@ public function setImageSizeY($imageSizeY) } /** + * Set value + * * @param ProductCustomOptionValuesInterface[] $values * @return $this */ @@ -826,7 +852,7 @@ public function setValues(array $values = null) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Catalog\Api\Data\ProductCustomOptionExtensionInterface|null */ @@ -852,6 +878,8 @@ public function getRegularPrice() } /** + * Get Product Option Collection + * * @param Product $product * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection */ @@ -882,7 +910,7 @@ public function getProductOptionCollection(Product $product) } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Catalog\Api\Data\ProductCustomOptionExtensionInterface $extensionAttributes * @return $this @@ -894,6 +922,8 @@ public function setExtensionAttributes( } /** + * Get option repository + * * @return Option\Repository */ private function getOptionRepository() @@ -906,6 +936,8 @@ private function getOptionRepository() } /** + * Get metadata pool + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() @@ -931,7 +963,7 @@ private function cleanFileExtensions() preg_match_all('/(?<extensions>[a-z0-9]+)/i', strtolower($rawExtensions), $matches); if (!empty($matches)) { $extensions = implode(', ', array_unique($matches['extensions'])); + $this->setFileExtension($extensions); } - $this->setFileExtension($extensions); } } From ccf86d3dbca0517135ca984061e92523d0f7ffc9 Mon Sep 17 00:00:00 2001 From: Daniel Sloof <goapsychadelic@gmail.com> Date: Mon, 26 Aug 2019 11:10:41 +0200 Subject: [PATCH 402/841] add Magento_QuoteGraphQl dependency to Magento_ConfigurableProductGraphQl The Magento_ConfigurableProductGraphQl module has a reference to CartItemInput. This type is defined in Magento_QuoteGraphQl. --- app/code/Magento/ConfigurableProductGraphQl/etc/module.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml index 98e7957d0af8e..f249a417f1046 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml @@ -12,6 +12,7 @@ <module name="Magento_ConfigurableProduct"/> <module name="Magento_GraphQl"/> <module name="Magento_CatalogGraphQl"/> + <module name="Magento_QuoteGraphQl"/> </sequence> </module> </config> From 8c33bbd093c9680f2e9d04616818047ec9893203 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Mon, 26 Aug 2019 14:08:52 +0300 Subject: [PATCH 403/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Replace using category collection to using db. --- .../Catalog/Model/ResourceModel/Category.php | 40 ++++++++++++++++++ .../ResourceModel/Product/Collection.php | 41 +++++++++++-------- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 536fda7e093d3..3794dbf46579f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -9,11 +9,14 @@ * * @author Magento Core Team <core@magentocommerce.com> */ +declare(strict_types=1); + namespace Magento\Catalog\Model\ResourceModel; use Magento\Catalog\Model\Indexer\Category\Product\Processor; use Magento\Framework\DataObject; use Magento\Framework\EntityManager\EntityManager; +use Magento\Catalog\Setup\CategorySetup; /** * Resource model for category entity @@ -1132,4 +1135,41 @@ private function getAggregateCount() } return $this->aggregateCount; } + + /** + * Get category with children. + * + * @param int $categoryId + * @return array + */ + public function getCategoryWithChildren(int $categoryId): array + { + $connection = $this->getConnection(); + + $select = $connection->select() + ->from( + ['eav_attribute' => $this->getTable('eav_attribute')], + ['attribute_id'] + )->where('entity_type_id = ?', CategorySetup::CATEGORY_ENTITY_TYPE_ID) + ->where('attribute_code = ?', 'is_anchor') + ->limit(1); + $attributeId = $connection->fetchRow($select); + + $select = $connection->select() + ->from( + ['cce' => $this->getTable('catalog_category_entity')], + ['entity_id', 'parent_id', 'path'] + )->join( + ['cce_int' => $this->getTable('catalog_category_entity_int')], + 'cce.entity_id = cce_int.entity_id', + ['is_anchor' => 'cce_int.value'] + )->where( + 'cce_int.attribute_id = ?', + $attributeId['attribute_id'] + )->where( + "cce.path LIKE '%/{$categoryId}' OR cce.path LIKE '%/{$categoryId}/%'" + )->order('path'); + + return $connection->fetchAll($select); + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index dffa49d97838c..71b03620b342b 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -23,7 +23,7 @@ use Magento\Framework\Indexer\DimensionFactory; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Store\Model\Store; -use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory; +use Magento\Catalog\Model\ResourceModel\Category; /** * Product collection @@ -305,9 +305,9 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac private $urlFinder; /** - * @var CollectionFactory + * @var Category */ - private $categoryCollectionFactory; + private $categoryResourceModel; /** * Collection constructor @@ -337,7 +337,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac * @param TableMaintainer|null $tableMaintainer * @param PriceTableResolver|null $priceTableResolver * @param DimensionFactory|null $dimensionFactory - * @param CollectionFactory|null $categoryCollectionFactory + * @param Category|null $categoryResourceModel * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -367,7 +367,7 @@ public function __construct( TableMaintainer $tableMaintainer = null, PriceTableResolver $priceTableResolver = null, DimensionFactory $dimensionFactory = null, - CollectionFactory $categoryCollectionFactory = null + Category $categoryResourceModel = null ) { $this->moduleManager = $moduleManager; $this->_catalogProductFlatState = $catalogProductFlatState; @@ -401,8 +401,8 @@ public function __construct( $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class); $this->dimensionFactory = $dimensionFactory ?: ObjectManager::getInstance()->get(DimensionFactory::class); - $this->categoryCollectionFactory = $categoryCollectionFactory ?: ObjectManager::getInstance() - ->get(CollectionFactory::class); + $this->categoryResourceModel = $categoryResourceModel ?: ObjectManager::getInstance() + ->get(Category::class); } /** @@ -2104,18 +2104,23 @@ protected function _applyZeroStoreProductLimitations() private function getChildrenCategories(int $categoryId): array { $categoryIds[] = $categoryId; - - /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection */ - $categoryCollection = $this->categoryCollectionFactory->create(); - $categories = $categoryCollection - ->addAttributeToFilter( - ['is_anchor', 'path'], - [1, ['like' => $categoryId . '/%']] - )->getItems(); - foreach ($categories as $category) { - $categoryChildren = $category->getChildren(); - $categoryIds = array_merge($categoryIds, explode(',', $categoryChildren)); + $anchorCategory = []; + + $categories = $this->categoryResourceModel->getCategoryWithChildren($categoryId); + $firstCategory = array_shift($categories); + if ($firstCategory['is_anchor'] == 1) { + $anchorCategory[] = (int)$firstCategory['entity_id']; + foreach ($categories as $category) { + if (in_array($category['parent_id'], $categoryIds) + && in_array($category['parent_id'], $anchorCategory)) { + $categoryIds[] = (int)$category['entity_id']; + if ($category['is_anchor'] == 1) { + $anchorCategory[] = (int)$category['entity_id']; + } + } + } } + return $categoryIds; } From 58036f0cc7c45ed77e84718df1475c791fd29200 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Sat, 24 Aug 2019 01:16:33 -0400 Subject: [PATCH 404/841] Clone product before adding to cart This prevents undesired behavior when adding multiple variants of the same SKU to the cart in a single request. Without cloning the product when adding to, the product object set with the quote item is a referenced object. When adding a n+1 cart item with same sku, but different options multiple cart items are referencing the same instance of a product object. This causes Item::representProduct to ignore custom option and variant differences. Fixes magento/graphql-ce#854 --- .../Model/Cart/AddSimpleProductToCart.php | 2 +- .../AddConfigurableProductToCartTest.php | 72 +++++++++++++++++++ 2 files changed, 73 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index fb2a9706792cc..011b914fba329 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -55,7 +55,7 @@ public function execute(Quote $cart, array $cartItemData): void $sku = $this->extractSku($cartItemData); try { - $product = $this->productRepository->get($sku); + $product = clone $this->productRepository->get($sku); } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku])); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php index 0e334999599b4..64e3872069f5a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php @@ -67,6 +67,78 @@ public function testAddConfigurableProductToCart() self::assertArrayHasKey('value_label', $option); } + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testAddMultipleConfigurableProductToCart() + { + $searchResponse = $this->graphQlQuery($this->getFetchProductQuery('configurable')); + $product = current($searchResponse['products']['items']); + + $quantityOne = 1; + $quantityTwo = 2; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $parentSku = $product['sku']; + $skuOne = 'simple_10'; + $skuTwo = 'simple_20'; + $valueIdOne = $product['configurable_options'][0]['values'][0]['value_index']; + + $query = <<<QUERY +mutation { + addConfigurableProductsToCart(input:{ + cart_id:"{$maskedQuoteId}" + cart_items:[ + { + parent_sku:"{$parentSku}" + data:{ + sku:"{$skuOne}" + quantity:{$quantityOne} + } + } + { + parent_sku:"{$parentSku}" + data:{ + sku:"{$skuTwo}" + quantity:{$quantityTwo} + } + } + ] + }) { + cart { + items { + id + quantity + product { + sku + } + ... on ConfigurableCartItem { + configurable_options { + option_label + value_label + value_id + } + } + } + } + } +} +QUERY; + + $response = $this->graphQlMutation($query); + + $cartItems = $response['addConfigurableProductsToCart']['cart']['items']; + self::assertCount(2, $cartItems); + + foreach ($cartItems as $cartItem) { + if ($cartItem['configurable_options'][0]['value_id'] === $valueIdOne) { + self::assertEquals($quantityOne, $cartItem['quantity']); + } else { + self::assertEquals($quantityTwo, $cartItem['quantity']); + } + } + } + /** * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable_sku.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php From c43eac4f7069a565746f7d03eab07be82e465b98 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 26 Aug 2019 20:20:31 +0700 Subject: [PATCH 405/841] Move creditmemo locale revert to below insertTotal --- app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php index cea4086b6ba83..f1430757939e7 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Creditmemo.php @@ -184,6 +184,9 @@ public function getPdf($creditmemos = []) } /* Add totals */ $this->insertTotals($page, $creditmemo); + if ($creditmemo->getStoreId()) { + $this->_localeResolver->revert(); + } } $this->_afterGetPdf(); return $pdf; From aad06333dacf9ccc470076491fc9e2cc029c3e84 Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Mon, 26 Aug 2019 16:06:00 +0200 Subject: [PATCH 406/841] MC-18221: Fix functional tests reset auto code format changes --- .../Paypal/Model/Payflow/Transparent.php | 14 +- .../Magento/Quote/Model/QuoteManagement.php | 2 +- .../Magento/Sales/Model/Order/Payment.php | 2 +- app/code/Magento/User/Model/User.php | 4 +- composer.lock | 539 +++++++++++------- .../Model/AsyncScheduleMultiStoreTest.php | 18 +- .../Webapi/ServiceInputProcessor.php | 2 +- 7 files changed, 363 insertions(+), 218 deletions(-) diff --git a/app/code/Magento/Paypal/Model/Payflow/Transparent.php b/app/code/Magento/Paypal/Model/Payflow/Transparent.php index f58fc2f78a30a..6569bdb20edfe 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Transparent.php +++ b/app/code/Magento/Paypal/Model/Payflow/Transparent.php @@ -7,18 +7,18 @@ namespace Magento\Paypal\Model\Payflow; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Exception\State\InvalidTransitionException; use Magento\Payment\Helper\Formatter; use Magento\Payment\Model\InfoInterface; -use Magento\Payment\Model\Method\ConfigInterfaceFactory; -use Magento\Payment\Model\Method\TransparentInterface; -use Magento\Paypal\Model\Payflow\Service\Gateway; -use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface; -use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator; use Magento\Paypal\Model\Payflowpro; use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; use Magento\Sales\Model\Order\Payment; +use Magento\Paypal\Model\Payflow\Service\Gateway; +use Magento\Framework\Exception\LocalizedException; +use Magento\Payment\Model\Method\TransparentInterface; +use Magento\Payment\Model\Method\ConfigInterfaceFactory; +use Magento\Framework\Exception\State\InvalidTransitionException; +use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface; +use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator; use Magento\Vault\Api\Data\PaymentTokenInterface; use Magento\Vault\Api\Data\PaymentTokenInterfaceFactory; diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index 3dfa89d1fca5a..84ef699b6209e 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -14,9 +14,9 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\StateException; use Magento\Quote\Api\Data\PaymentInterface; -use Magento\Quote\Model\Quote as QuoteEntity; use Magento\Quote\Model\Quote\Address\ToOrder as ToOrderConverter; use Magento\Quote\Model\Quote\Address\ToOrderAddress as ToOrderAddressConverter; +use Magento\Quote\Model\Quote as QuoteEntity; use Magento\Quote\Model\Quote\Item\ToOrderItem as ToOrderItemConverter; use Magento\Quote\Model\Quote\Payment\ToOrderPayment as ToOrderPaymentConverter; use Magento\Sales\Api\Data\OrderInterfaceFactory as OrderFactory; diff --git a/app/code/Magento/Sales/Model/Order/Payment.php b/app/code/Magento/Sales/Model/Order/Payment.php index 23a1ef2e689b7..dcf6d86b44cae 100644 --- a/app/code/Magento/Sales/Model/Order/Payment.php +++ b/app/code/Magento/Sales/Model/Order/Payment.php @@ -8,13 +8,13 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; -use Magento\Sales\Api\CreditmemoManagementInterface as CreditmemoManager; use Magento\Sales\Api\Data\OrderPaymentInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment\Info; use Magento\Sales\Model\Order\Payment\Transaction; use Magento\Sales\Model\Order\Payment\Transaction\ManagerInterface; +use Magento\Sales\Api\CreditmemoManagementInterface as CreditmemoManager; /** * Order payment information diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 6294775149567..d79f2013241e6 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -7,14 +7,14 @@ namespace Magento\User\Model; use Magento\Backend\Model\Auth\Credential\StorageInterface; -use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ObjectManager; -use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Serialize\Serializer\Json; use Magento\User\Api\Data\UserInterface; use Magento\User\Model\Spi\NotificationExceptionInterface; use Magento\User\Model\Spi\NotificatorInterface; +use Magento\Framework\App\DeploymentConfig; /** * Admin user model diff --git a/composer.lock b/composer.lock index 340fb386a3654..7f50f20b13321 100644 --- a/composer.lock +++ b/composer.lock @@ -201,16 +201,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.2.3", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5" + "reference": "33810d865dd06a674130fceb729b2f279dc79e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f26a67e397be0e5c00d7c52ec7b5010098e15ce5", - "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/33810d865dd06a674130fceb729b2f279dc79e8c", + "reference": "33810d865dd06a674130fceb729b2f279dc79e8c", "shasum": "" }, "require": { @@ -253,20 +253,20 @@ "ssl", "tls" ], - "time": "2019-08-02T09:05:43+00:00" + "time": "2019-07-31T08:13:16+00:00" }, { "name": "composer/composer", - "version": "1.9.0", + "version": "1.8.6", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" + "reference": "19b5f66a0e233eb944f134df34091fe1c5dfcc11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", - "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "url": "https://api.github.com/repos/composer/composer/zipball/19b5f66a0e233eb944f134df34091fe1c5dfcc11", + "reference": "19b5f66a0e233eb944f134df34091fe1c5dfcc11", "shasum": "" }, "require": { @@ -302,7 +302,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -326,14 +326,14 @@ "homepage": "http://seld.be" } ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", "homepage": "https://getcomposer.org/", "keywords": [ "autoload", "dependency", "package" ], - "time": "2019-08-02T18:55:33+00:00" + "time": "2019-06-11T13:03:06+00:00" }, { "name": "composer/semver", @@ -2359,16 +2359,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.12.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", - "reference": "550ebaac289296ce228a706d0867afc34687e3f4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -2380,7 +2380,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -2396,13 +2396,13 @@ "MIT" ], "authors": [ - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -2413,20 +2413,20 @@ "polyfill", "portable" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.12.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", - "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -2438,7 +2438,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -2472,7 +2472,7 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", @@ -3151,16 +3151,16 @@ }, { "name": "zendframework/zend-diactoros", - "version": "1.8.7", + "version": "1.8.6", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b" + "reference": "20da13beba0dde8fb648be3cc19765732790f46e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/a85e67b86e9b8520d07e6415fcbcb8391b44a75b", - "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", + "reference": "20da13beba0dde8fb648be3cc19765732790f46e", "shasum": "" }, "require": { @@ -3180,7 +3180,9 @@ "type": "library", "extra": { "branch-alias": { - "dev-release-1.8": "1.8.x-dev" + "dev-master": "1.8.x-dev", + "dev-develop": "1.9.x-dev", + "dev-release-2.0": "2.0.x-dev" } }, "autoload": { @@ -3209,7 +3211,7 @@ "psr", "psr-7" ], - "time": "2019-08-06T17:53:53+00:00" + "time": "2018-09-05T19:29:37+00:00" }, { "name": "zendframework/zend-escaper", @@ -4929,26 +4931,25 @@ }, { "name": "allure-framework/allure-php-api", - "version": "1.1.5", + "version": "1.1.4", "source": { "type": "git", - "url": "https://github.com/allure-framework/allure-php-commons.git", - "reference": "c7a675823ad75b8e02ddc364baae21668e7c4e88" + "url": "https://github.com/allure-framework/allure-php-adapter-api.git", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-commons/zipball/c7a675823ad75b8e02ddc364baae21668e7c4e88", - "reference": "c7a675823ad75b8e02ddc364baae21668e7c4e88", + "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", "shasum": "" }, "require": { - "jms/serializer": "^0.16.0", + "jms/serializer": ">=0.16.0", + "moontoast/math": ">=1.1.0", "php": ">=5.4.0", - "ramsey/uuid": "^3.0.0", - "symfony/http-foundation": "^2.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0.0" + "phpunit/phpunit": ">=4.0.0", + "ramsey/uuid": ">=3.0.0", + "symfony/http-foundation": ">=2.0" }, "type": "library", "autoload": { @@ -4978,7 +4979,7 @@ "php", "report" ], - "time": "2018-05-25T14:02:11+00:00" + "time": "2016-12-07T12:15:46+00:00" }, { "name": "allure-framework/allure-phpunit", @@ -5909,6 +5910,76 @@ ], "time": "2019-03-25T19:12:02+00:00" }, + { + "name": "doctrine/collections", + "version": "v1.6.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/c5e0bc17b1620e97c968ac409acbff28b8b850be", + "reference": "c5e0bc17b1620e97c968ac409acbff28b8b850be", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.2.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "time": "2019-06-09T13:48:14+00:00" + }, { "name": "doctrine/instantiator", "version": "1.2.0", @@ -6025,6 +6096,52 @@ ], "time": "2019-06-08T11:03:04+00:00" }, + { + "name": "epfremme/swagger-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/epfremmer/swagger-php.git", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.2", + "doctrine/collections": "^1.3", + "jms/serializer": "^1.1", + "php": ">=5.5", + "phpoption/phpoption": "^1.1", + "symfony/yaml": "^2.7|^3.1" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "~4.8|~5.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "package", + "autoload": { + "psr-4": { + "Epfremme\\Swagger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Edward Pfremmer", + "email": "epfremme@nerdery.com" + } + ], + "description": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", + "time": "2016-09-26T17:24:17+00:00" + }, { "name": "facebook/webdriver", "version": "1.7.1", @@ -6359,48 +6476,6 @@ "description": "Expands internal property references in a yaml file.", "time": "2017-12-16T16:06:03+00:00" }, - { - "name": "ircmaxell/password-compat", - "version": "v1.0.4", - "source": { - "type": "git", - "url": "https://github.com/ircmaxell/password_compat.git", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "4.*" - }, - "type": "library", - "autoload": { - "files": [ - "lib/password.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anthony Ferrara", - "email": "ircmaxell@php.net", - "homepage": "http://blog.ircmaxell.com" - } - ], - "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", - "homepage": "https://github.com/ircmaxell/password_compat", - "keywords": [ - "hashing", - "password" - ], - "time": "2014-11-20T16:49:30+00:00" - }, { "name": "jms/metadata", "version": "1.7.0", @@ -6493,44 +6568,56 @@ }, { "name": "jms/serializer", - "version": "0.16.0", + "version": "1.14.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9" + "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9", - "reference": "c8a171357ca92b6706e395c757f334902d430ea9", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", + "reference": "ee96d57024af9a7716d56fcbe3aa94b3d030f3ca", "shasum": "" }, "require": { - "doctrine/annotations": "1.*", - "jms/metadata": "~1.1", + "doctrine/annotations": "^1.0", + "doctrine/instantiator": "^1.0.3", + "jms/metadata": "^1.3", "jms/parser-lib": "1.*", - "php": ">=5.3.2", - "phpcollection/phpcollection": "~0.1" + "php": "^5.5|^7.0", + "phpcollection/phpcollection": "~0.1", + "phpoption/phpoption": "^1.1" + }, + "conflict": { + "twig/twig": "<1.12" }, "require-dev": { "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "~1.0.1", - "jackalope/jackalope-doctrine-dbal": "1.0.*", + "doctrine/phpcr-odm": "^1.3|^2.0", + "ext-pdo_sqlite": "*", + "jackalope/jackalope-doctrine-dbal": "^1.1.5", + "phpunit/phpunit": "^4.8|^5.0", "propel/propel1": "~1.7", - "symfony/filesystem": "2.*", - "symfony/form": "~2.1", - "symfony/translation": "~2.0", - "symfony/validator": "~2.0", - "symfony/yaml": "2.*", - "twig/twig": ">=1.8,<2.0-dev" + "psr/container": "^1.0", + "symfony/dependency-injection": "^2.7|^3.3|^4.0", + "symfony/expression-language": "^2.6|^3.0", + "symfony/filesystem": "^2.1", + "symfony/form": "~2.1|^3.0", + "symfony/translation": "^2.1|^3.0", + "symfony/validator": "^2.2|^3.0", + "symfony/yaml": "^2.1|^3.0", + "twig/twig": "~1.12|~2.0" }, "suggest": { + "doctrine/cache": "Required if you like to use cache functionality.", + "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", "symfony/yaml": "Required if you'd like to serialize data to YAML format." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "0.15-dev" + "dev-1.x": "1.14-dev" } }, "autoload": { @@ -6540,14 +6627,16 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache2" + "MIT" ], "authors": [ { - "name": "Johannes Schmitt", - "role": "Developer of wrapped JMSSerializerBundle", - "email": "schmittjoh@gmail.com", - "homepage": "https://github.com/schmittjoh" + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" } ], "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", @@ -6559,7 +6648,7 @@ "serialization", "xml" ], - "time": "2014-03-18T08:39:00+00:00" + "time": "2019-04-17T08:12:16+00:00" }, { "name": "league/container", @@ -6800,23 +6889,23 @@ }, { "name": "mikey179/vfsstream", - "version": "v1.6.7", + "version": "v1.6.6", "source": { "type": "git", "url": "https://github.com/bovigo/vfsStream.git", - "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb" + "reference": "095238a0711c974ae5b4ebf4c4534a23f3f6c99d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb", - "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/095238a0711c974ae5b4ebf4c4534a23f3f6c99d", + "reference": "095238a0711c974ae5b4ebf4c4534a23f3f6c99d", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "^4.5|^5.0" + "phpunit/phpunit": "~4.5" }, "type": "library", "extra": { @@ -6842,7 +6931,56 @@ ], "description": "Virtual file system to mock the real file system in unit tests.", "homepage": "http://vfs.bovigo.org/", - "time": "2019-08-01T01:38:37+00:00" + "time": "2019-04-08T13:54:32+00:00" + }, + { + "name": "moontoast/math", + "version": "1.1.2", + "source": { + "type": "git", + "url": "https://github.com/ramsey/moontoast-math.git", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "^0.9.0", + "phpunit/phpunit": "^4.7|>=5.0 <5.4", + "satooshi/php-coveralls": "^0.6.1", + "squizlabs/php_codesniffer": "^2.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Moontoast\\Math\\": "src/Moontoast/Math/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A mathematics library, providing functionality for large numbers", + "homepage": "https://github.com/ramsey/moontoast-math", + "keywords": [ + "bcmath", + "math" + ], + "time": "2017-02-16T16:54:46+00:00" }, { "name": "mustache/mustache", @@ -8863,31 +9001,31 @@ }, { "name": "symfony/http-foundation", - "version": "v2.8.50", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "746f8d3638bf46ee8b202e62f2b214c3d61fb06a" + "reference": "8b778ee0c27731105fbf1535f51793ad1ae0ba2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/746f8d3638bf46ee8b202e62f2b214c3d61fb06a", - "reference": "746f8d3638bf46ee8b202e62f2b214c3d61fb06a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8b778ee0c27731105fbf1535f51793ad1ae0ba2b", + "reference": "8b778ee0c27731105fbf1535f51793ad1ae0ba2b", "shasum": "" }, "require": { - "php": ">=5.3.9", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php54": "~1.0", - "symfony/polyfill-php55": "~1.0" + "php": "^7.1.3", + "symfony/mime": "^4.3", + "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { - "symfony/expression-language": "~2.4|~3.0.0" + "predis/predis": "~1.0", + "symfony/expression-language": "~3.4|~4.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -8914,24 +9052,30 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-04-16T10:00:53+00:00" + "time": "2019-07-23T11:21:36+00:00" }, { - "name": "symfony/options-resolver", + "name": "symfony/mime", "version": "v4.3.3", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "40762ead607c8f792ee4516881369ffa553fee6f" + "url": "https://github.com/symfony/mime.git", + "reference": "6b7148029b1dd5eda1502064f06d01357b7b2d8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40762ead607c8f792ee4516881369ffa553fee6f", - "reference": "40762ead607c8f792ee4516881369ffa553fee6f", + "url": "https://api.github.com/repos/symfony/mime/zipball/6b7148029b1dd5eda1502064f06d01357b7b2d8b", + "reference": "6b7148029b1dd5eda1502064f06d01357b7b2d8b", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "egulias/email-validator": "^2.0", + "symfony/dependency-injection": "~3.4|^4.1" }, "type": "library", "extra": { @@ -8941,7 +9085,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" + "Symfony\\Component\\Mime\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -8961,47 +9105,43 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "A library to manipulate MIME messages", "homepage": "https://symfony.com", "keywords": [ - "config", - "configuration", - "options" + "mime", + "mime-type" ], - "time": "2019-06-13T11:01:17+00:00" + "time": "2019-07-19T16:21:19+00:00" }, { - "name": "symfony/polyfill-php54", - "version": "v1.12.0", + "name": "symfony/options-resolver", + "version": "v4.3.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "a043bcced870214922fbb4bf22679d431ec0296a" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "40762ead607c8f792ee4516881369ffa553fee6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/a043bcced870214922fbb4bf22679d431ec0296a", - "reference": "a043bcced870214922fbb4bf22679d431ec0296a", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/40762ead607c8f792ee4516881369ffa553fee6f", + "reference": "40762ead607c8f792ee4516881369ffa553fee6f", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.1.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "4.3-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php54\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -9010,51 +9150,54 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 5.4+ features to lower PHP versions", + "description": "Symfony OptionsResolver Component", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "config", + "configuration", + "options" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-06-13T11:01:17+00:00" }, { - "name": "symfony/polyfill-php55", - "version": "v1.12.0", + "name": "symfony/polyfill-intl-idn", + "version": "v1.11.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "548bb39407e78e54f785b4e18c7e0d5d9e493265" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/548bb39407e78e54f785b4e18c7e0d5d9e493265", - "reference": "548bb39407e78e54f785b4e18c7e0d5d9e493265", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c766e95bec706cdd89903b1eda8afab7d7a6b7af", + "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af", "shasum": "" }, "require": { - "ircmaxell/password-compat": "~1.0", - "php": ">=5.3.3" + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.9-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php55\\": "" + "Symfony\\Polyfill\\Intl\\Idn\\": "" }, "files": [ "bootstrap.php" @@ -9065,37 +9208,39 @@ "MIT" ], "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" + }, + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" } ], - "description": "Symfony polyfill backporting some PHP 5.5+ features to lower PHP versions", + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "idn", + "intl", "polyfill", "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-03-04T13:44:35+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.12.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "54b4c428a0054e254223797d2713c31e08610831" + "reference": "bc4858fb611bda58719124ca079baff854149c89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/54b4c428a0054e254223797d2713c31e08610831", - "reference": "54b4c428a0054e254223797d2713c31e08610831", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/bc4858fb611bda58719124ca079baff854149c89", + "reference": "bc4858fb611bda58719124ca079baff854149c89", "shasum": "" }, "require": { @@ -9105,7 +9250,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -9141,20 +9286,20 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.12.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "04ce3335667451138df4307d6a9b61565560199e" + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", - "reference": "04ce3335667451138df4307d6a9b61565560199e", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", "shasum": "" }, "require": { @@ -9163,7 +9308,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.12-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -9196,7 +9341,7 @@ "portable", "shim" ], - "time": "2019-08-06T08:03:45+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/service-contracts", @@ -9308,20 +9453,20 @@ }, { "name": "symfony/yaml", - "version": "v4.3.3", + "version": "v3.4.30", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6" + "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/34d29c2acd1ad65688f58452fd48a46bd996d5a6", - "reference": "34d29c2acd1ad65688f58452fd48a46bd996d5a6", + "url": "https://api.github.com/repos/symfony/yaml/zipball/051d045c684148060ebfc9affb7e3f5e0899d40b", + "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b", "shasum": "" }, "require": { - "php": "^7.1.3", + "php": "^5.5.9|>=7.0.8", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -9336,7 +9481,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -9363,7 +9508,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-07-24T14:47:54+00:00" + "time": "2019-07-24T13:01:31+00:00" }, { "name": "theseer/fdomdocument", diff --git a/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php index e52207b807fc5..b1f86739786c2 100644 --- a/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php +++ b/dev/tests/api-functional/testsuite/Magento/WebapiAsync/Model/AsyncScheduleMultiStoreTest.php @@ -9,20 +9,20 @@ namespace Magento\WebapiAsync\Model; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Api\Data\ProductInterface as Product; -use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\MessageQueue\PreconditionFailedException; +use Magento\TestFramework\MessageQueue\PublisherConsumerController; +use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException; +use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\TestFramework\Helper\Bootstrap; use Magento\Catalog\Model\ResourceModel\Product\Collection; -use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Phrase; use Magento\Framework\Registry; use Magento\Framework\Webapi\Exception; -use Magento\Framework\Webapi\Rest\Request; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Api\Data\ProductInterface as Product; +use Magento\Framework\ObjectManagerInterface; use Magento\Store\Model\Store; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\MessageQueue\EnvironmentPreconditionException; -use Magento\TestFramework\MessageQueue\PreconditionFailedException; -use Magento\TestFramework\MessageQueue\PublisherConsumerController; -use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\Framework\Webapi\Rest\Request; /** * Check async request for multistore product creation service, scheduling bulk diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 5a014380bc0e6..f93d7efda5c8a 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -19,8 +19,8 @@ use Magento\Framework\Phrase; use Magento\Framework\Reflection\MethodsMap; use Magento\Framework\Reflection\TypeProcessor; -use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; use Magento\Framework\Webapi\Exception as WebapiException; +use Magento\Framework\Webapi\CustomAttribute\PreprocessorInterface; use Zend\Code\Reflection\ClassReflection; /** From d66f6c39cb2c87b8765bf3f44b173ae2673aef7d Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 26 Aug 2019 21:09:34 +0700 Subject: [PATCH 407/841] Resolve undefined variable in ReviewPayment class --- .../Adminhtml/Order/ReviewPayment.php | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php index 09a52a113617a..93c8305ec2396 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/ReviewPayment.php @@ -3,11 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Sales\Controller\Adminhtml\Order; use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; -class ReviewPayment extends \Magento\Sales\Controller\Adminhtml\Order +/** + * Class \Magento\Sales\Controller\Adminhtml\Order\ReviewPayment + */ +class ReviewPayment extends \Magento\Sales\Controller\Adminhtml\Order implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -21,7 +28,7 @@ class ReviewPayment extends \Magento\Sales\Controller\Adminhtml\Order * * Either denies or approves a payment that is in "review" state * - * @return \Magento\Backend\Model\View\Result\Redirect + * @return \Magento\Framework\Controller\Result\Redirect */ public function execute() { @@ -50,21 +57,23 @@ public function execute() } break; default: - throw new \Exception(sprintf('Action "%s" is not supported.', $action)); + throw new \Magento\Framework\Exception\NotFoundException( + __('Action "%1" is not supported.', $action) + ); } $this->orderRepository->save($order); $this->messageManager->addSuccessMessage($message); + $resultRedirect->setPath('sales/order/view', ['order_id' => $order->getEntityId()]); } else { $resultRedirect->setPath('sales/*/'); return $resultRedirect; } + // phpcs:ignore Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addErrorMessage($e->getMessage()); - } catch (\Exception $e) { $this->messageManager->addErrorMessage(__('We can\'t update the payment right now.')); $this->logger->critical($e); + $resultRedirect->setPath('sales/*/'); } - $resultRedirect->setPath('sales/order/view', ['order_id' => $order->getEntityId()]); return $resultRedirect; } } From 87a4c5736ab17786c7dca49d29476a895837cd18 Mon Sep 17 00:00:00 2001 From: Bohdan Korablov <korablov@adobe.com> Date: Mon, 26 Aug 2019 09:16:49 -0500 Subject: [PATCH 408/841] MC-19250: The stuck deployment on the Cloud because of consumers --- .../UseCase/WaitAndNotWaitMessagesTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php index 62040018ffd3e..ba5809b6634c2 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php @@ -67,8 +67,8 @@ public function testWaitForMessages() { $this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 1]], $this->config); - foreach ($this->messages as $msg) { - $this->publishMessage($msg); + foreach ($this->messages as $message) { + $this->publishMessage($message); } $this->waitForAsynchronousResult(count($this->messages), $this->logFilePath); @@ -94,8 +94,8 @@ public function testNotWaitForMessages(): void $this->writeConfig($config); $this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 0]], $this->loadConfig()); - foreach ($this->messages as $msg) { - $this->publishMessage($msg); + foreach ($this->messages as $message) { + $this->publishMessage($message); } $this->publisherConsumerController->startConsumers(); @@ -113,11 +113,11 @@ public function testNotWaitForMessages(): void } /** - * @param string $msg + * @param string $message */ - private function publishMessage(string $msg): void + private function publishMessage(string $message): void { - $this->msgObject->setValue($msg); + $this->msgObject->setValue($message); $this->msgObject->setTextFilePath($this->logFilePath); $this->publisher->publish('multi.topic.queue.topic.c', $this->msgObject); } From 3ccf1d99d3db16ae2dc9a6ebd97e62b931af6720 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Mon, 26 Aug 2019 17:43:38 +0300 Subject: [PATCH 409/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix magento category installation. --- app/code/Magento/Catalog/Model/ResourceModel/Category.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index cfc82d9b4ae94..d50570930923e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -278,7 +278,7 @@ protected function _beforeSave(\Magento\Framework\DataObject $object) if ($object->getPosition() === null) { $object->setPosition($this->_getMaxPosition($object->getPath()) + 1); } - $path = explode('/', $object->getPath()); + $path = explode('/', (string)$object->getPath()); $level = count($path) - ($object->getId() ? 1 : 0); $toUpdateChild = array_diff($path, [$object->getId()]); @@ -317,7 +317,7 @@ protected function _afterSave(\Magento\Framework\DataObject $object) /** * Add identifier for new category */ - if (substr($object->getPath(), -1) == '/') { + if (substr((string)$object->getPath(), -1) == '/') { $object->setPath($object->getPath() . $object->getId()); $this->_savePath($object); } From 3babf2ae50c7ec79aa5bd2a6906a244f6f4cfed5 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Mon, 26 Aug 2019 09:47:19 -0500 Subject: [PATCH 410/841] Code Style changes --- .../CatalogGraphQl/Model/Category/GetRootCategoryId.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php index c8b80622433cb..22687851a2de9 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php @@ -11,6 +11,9 @@ use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; +/** + * Gets Root Category for Current Store. + */ class GetRootCategoryId { /** @@ -50,3 +53,4 @@ public function execute() return $this->rootCategoryId; } } + From 5e9f148e0de9a78bd420c050c4b919e4b7628ad5 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Mon, 26 Aug 2019 10:49:37 -0400 Subject: [PATCH 411/841] Use forcereload instead of clone --- .../Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php index 011b914fba329..11719db2d1b8f 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php @@ -55,7 +55,7 @@ public function execute(Quote $cart, array $cartItemData): void $sku = $this->extractSku($cartItemData); try { - $product = clone $this->productRepository->get($sku); + $product = $this->productRepository->get($sku, false, null, true); } catch (NoSuchEntityException $e) { throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku])); } From 3f2f2bcc4ae99fb57c932125a7531e63c19e3136 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 26 Aug 2019 10:13:03 -0500 Subject: [PATCH 412/841] MC-19572: Fix filtering by attribute of type select/multiselect using filter input type in - added test and fixtures for multiselect --- .../GraphQl/Catalog/ProductSearchTest.php | 116 ++++++++++++++++-- ..._attribute_layered_navigation_rollback.php | 11 ++ ..._navigation_with_multiselect_attribute.php | 98 +++++++++++++++ ...on_with_multiselect_attribute_rollback.php | 32 +++++ 4 files changed, 246 insertions(+), 11 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index a565ac656124a..6356f7b44db84 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -95,7 +95,7 @@ public function testFilterLn() } /** - * Advanced Search which uses product attribute to filter out the results + * Filter products using custom attribute of input type select(dropdown) and filterTypeInput eq * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -105,7 +105,42 @@ public function testAdvancedSearchByOneCustomAttribute() CacheCleaner::cleanAll(); $attributeCode = 'second_test_configurable'; $optionValue = $this->getDefaultAttributeOptionValue($attributeCode); - $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $optionValue); + $query = <<<QUERY +{ + products(filter:{ + $attributeCode: {eq: "{$optionValue}"} + } + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + + } + + } +} +QUERY; /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); @@ -151,6 +186,67 @@ public function testAdvancedSearchByOneCustomAttribute() ] ); } + /** + * Filter products using custom attribute of input type select(dropdown) and filterTypeInput eq + * + * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testFilterProductsByMultiSelectCustomAttribute() + { + CacheCleaner::cleanAll(); + $attributeCode = 'multiselect_attribute'; + /** @var \Magento\Eav\Model\Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); + /** @var AttributeOptionInterface[] $options */ + $options = $attribute->getOptions(); + array_shift($options); + $optionValues = array(); + for ($i = 0; $i < count($options); $i++) { + $optionValues[] = $options[$i]->getValue(); + } + $query = <<<QUERY +{ + products(filter:{ + $attributeCode: {in:["{$optionValues[0]}", "{$optionValues[1]}", "{$optionValues[2]}"]} + } + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + + } + + } +} +QUERY; + + $response = $this->graphQlQuery($query); + $this->assertEquals(3, $response['products']['total_count']); + $this->assertNotEmpty($response['products']['filters']); + } /** * Get the option value for the custom attribute to be used in the graphql query @@ -462,7 +558,7 @@ public function testLayeredNavigationWithConfigurableChildrenOutOfStock() /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); $outOfStockChildProduct = $productRepository->get('simple_30'); - // Set another child product from 2nd Configurable product with attribute option1 to OOS + // All child variations with this attribute are now set to Out of Stock $outOfStockChildProduct->setStockData( ['use_config_manage_stock' => 1, 'qty' => 0, @@ -784,18 +880,17 @@ public function testQueryProductsInCurrentPageSortedByPriceASC() products( filter: { - price:{gt: "5", lt: "50"} - or: - { - sku:{like:"simple%"} - name:{like:"simple%"} - } + price:{to :"50"} + sku:{like:"simple%"} + name:{like:"simple%"} + } pageSize:4 currentPage:1 sort: { price:ASC + name:ASC } ) { @@ -1062,7 +1157,7 @@ public function testQuerySortByPriceDESCWithDefaultPageSize() products( filter: { - sku:{like:"%simple%"} + sku:{like:"simple%"} } sort: { @@ -1109,7 +1204,6 @@ public function testQuerySortByPriceDESCWithDefaultPageSize() $this->assertEquals(20, $response['products']['page_info']['page_size']); $this->assertEquals(1, $response['products']['page_info']['current_page']); } - /** * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php index 49e2a8e88a1ac..2e4227eb35392 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php @@ -7,3 +7,14 @@ // phpcs:ignore Magento2.Security.IncludeFile require __DIR__ . '/../../ConfigurableProduct/_files/configurable_products_rollback.php'; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Api\AttributeRepositoryInterface; + +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + +/** @var AttributeRepositoryInterface $attributeRepository */ +$attributeRepository = Bootstrap::getObjectManager()->get(AttributeRepositoryInterface::class); +/** @var \Magento\Eav\Api\Data\AttributeInterface $attribute */ +$attribute = $attributeRepository->get('catalog_product', 'test_configurable'); +$attributeRepository->delete($attribute); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php new file mode 100644 index 0000000000000..7d4f22e154030 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * Create multiselect attribute + */ +require __DIR__ . '/multiselect_attribute.php'; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Api\AttributeRepositoryInterface; + +/** Create product with options and multiselect attribute */ + +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Setup\CategorySetup::class +); + +/** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ +$options = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class +); +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + +/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +$attribute = $eavConfig->getAttribute('catalog_product', 'multiselect_attribute'); + +$eavConfig->clear(); +$attribute->setIsSearchable(1) + ->setIsVisibleInAdvancedSearch(1) + ->setIsFilterable(true) + ->setIsFilterableInSearch(true) + ->setIsVisibleOnFront(1); +/** @var AttributeRepositoryInterface $attributeRepository */ +$attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); +$attributeRepository->save($attribute); + +$options->setAttributeFilter($attribute->getId()); +$optionIds = $options->getAllIds(); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId($optionIds[0] * 10) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setWebsiteIds([1]) + ->setName('With Multiselect 1 and 2') + ->setSku('simple_ms_1') + ->setPrice(10) + ->setDescription('Hello " &" Bring the water bottle when you can!') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setMultiselectAttribute([$optionIds[1],$optionIds[2]]) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); +$productRepository->save($product); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId($optionIds[1] * 10) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setWebsiteIds([1]) + ->setName('With Multiselect 2 and 3') + ->setSku('simple_ms_2') + ->setPrice(10) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setMultiselectAttribute([$optionIds[2], $optionIds[3]]) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); +$productRepository->save($product); + +$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setId($optionIds[2] * 10) + ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default')) + ->setWebsiteIds([1]) + ->setName('With Multiselect 1 and 3') + ->setSku('simple_ms_2') + ->setPrice(10) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setMultiselectAttribute([$optionIds[2], $optionIds[3]]) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); + +$productRepository->save($product); + +/** @var \Magento\Indexer\Model\Indexer\Collection $indexerCollection */ +$indexerCollection = Bootstrap::getObjectManager()->get(\Magento\Indexer\Model\Indexer\Collection::class); +$indexerCollection->load(); +/** @var \Magento\Indexer\Model\Indexer $indexer */ +foreach ($indexerCollection->getItems() as $indexer) { + $indexer->reindexAll(); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute_rollback.php new file mode 100644 index 0000000000000..eb8201f04e6cc --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/multiselect_attribute_rollback.php'; + +use Magento\Framework\Indexer\IndexerRegistry; + +/** + * Remove all products as strategy of isolation process + */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $productCollection \Magento\Catalog\Model\ResourceModel\Product */ +$productCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Catalog\Model\Product') + ->getCollection(); + +foreach ($productCollection as $product) { + $product->delete(); +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +\Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(IndexerRegistry::class) + ->get(Magento\CatalogInventory\Model\Indexer\Stock\Processor::INDEXER_ID) + ->reindexAll(); From 63e65299160b8b6e1af8bc6b83ef468e0ccabc5f Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 26 Aug 2019 10:16:05 -0500 Subject: [PATCH 413/841] MC-19432: REST API returns 404 error instead of 400 --- .../Quote/Model/Quote/Item/Repository.php | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/app/code/Magento/Quote/Model/Quote/Item/Repository.php b/app/code/Magento/Quote/Model/Quote/Item/Repository.php index 1fb0a2d7107f1..2b4852251bca2 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Repository.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Repository.php @@ -1,34 +1,40 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Quote\Model\Quote\Item; -use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Quote\Api\CartItemRepositoryInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartItemInterfaceFactory; -class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface +/** + * Repository for quote item. + */ +class Repository implements CartItemRepositoryInterface { /** * Quote repository. * - * @var \Magento\Quote\Api\CartRepositoryInterface + * @var CartRepositoryInterface */ protected $quoteRepository; /** * Product repository. * - * @var \Magento\Catalog\Api\ProductRepositoryInterface + * @var ProductRepositoryInterface */ protected $productRepository; /** - * @var \Magento\Quote\Api\Data\CartItemInterfaceFactory + * @var CartItemInterfaceFactory */ protected $itemDataFactory; @@ -43,25 +49,28 @@ class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface private $cartItemOptionsProcessor; /** - * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository - * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository - * @param \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory + * @param CartRepositoryInterface $quoteRepository + * @param ProductRepositoryInterface $productRepository + * @param CartItemInterfaceFactory $itemDataFactory + * @param CartItemOptionsProcessor $cartItemOptionsProcessor * @param CartItemProcessorInterface[] $cartItemProcessors */ public function __construct( - \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, - \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, - \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory, + CartRepositoryInterface $quoteRepository, + ProductRepositoryInterface $productRepository, + CartItemInterfaceFactory $itemDataFactory, + CartItemOptionsProcessor $cartItemOptionsProcessor, array $cartItemProcessors = [] ) { $this->quoteRepository = $quoteRepository; $this->productRepository = $productRepository; $this->itemDataFactory = $itemDataFactory; + $this->cartItemOptionsProcessor = $cartItemOptionsProcessor; $this->cartItemProcessors = $cartItemProcessors; } /** - * {@inheritdoc} + * @inheritdoc */ public function getList($cartId) { @@ -71,21 +80,26 @@ public function getList($cartId) /** @var \Magento\Quote\Model\Quote\Item $item */ foreach ($quote->getAllVisibleItems() as $item) { - $item = $this->getCartItemOptionsProcessor()->addProductOptions($item->getProductType(), $item); - $output[] = $this->getCartItemOptionsProcessor()->applyCustomOptions($item); + $item = $this->cartItemOptionsProcessor->addProductOptions($item->getProductType(), $item); + $output[] = $this->cartItemOptionsProcessor->applyCustomOptions($item); } return $output; } /** - * {@inheritdoc} + * @inheritdoc */ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) { /** @var \Magento\Quote\Model\Quote $quote */ $cartId = $cartItem->getQuoteId(); - $quote = $this->quoteRepository->getActive($cartId); + if (!$cartId) { + throw new InputException( + __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'cartId']) + ); + } + $quote = $this->quoteRepository->getActive($cartId); $quoteItems = $quote->getItems(); $quoteItems[] = $cartItem; $quote->setItems($quoteItems); @@ -95,7 +109,7 @@ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) } /** - * {@inheritdoc} + * @inheritdoc */ public function deleteById($cartId, $itemId) { @@ -116,17 +130,4 @@ public function deleteById($cartId, $itemId) return true; } - - /** - * @return CartItemOptionsProcessor - * @deprecated 100.1.0 - */ - private function getCartItemOptionsProcessor() - { - if (!$this->cartItemOptionsProcessor instanceof CartItemOptionsProcessor) { - $this->cartItemOptionsProcessor = ObjectManager::getInstance()->get(CartItemOptionsProcessor::class); - } - - return $this->cartItemOptionsProcessor; - } } From 13bb280eeda17d07741d32e2a45625d871a54db6 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Mon, 26 Aug 2019 10:54:32 -0500 Subject: [PATCH 414/841] magento/graphql-ce#685: Extract logic related to customer from DownloadableGraphQl module into new one - added composer dependency - removed some hard dependencies because of DependencyTest bug MC-19635 --- .../etc/module.xml | 2 -- .../DownloadableGraphQl/etc/module.xml | 1 - composer.json | 1 + composer.lock | 24 +++++++++---------- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml index 6dc201eb00ef9..fe513e7816ce7 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml +++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml @@ -8,8 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_CustomerDownloadableGraphQl" > <sequence> - <module name="Magento_Catalog"/> - <module name="Magento_Downloadable"/> <module name="Magento_GraphQl"/> <module name="Magento_CatalogGraphQl"/> <module name="Magento_DownloadableGraphQl"/> diff --git a/app/code/Magento/DownloadableGraphQl/etc/module.xml b/app/code/Magento/DownloadableGraphQl/etc/module.xml index e6158fdbe64eb..c06df1cf92d2f 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/module.xml +++ b/app/code/Magento/DownloadableGraphQl/etc/module.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_DownloadableGraphQl" > <sequence> - <module name="Magento_GraphQl"/> <module name="Magento_Catalog"/> <module name="Magento_CatalogGraphQl"/> <module name="Magento_QuoteGraphQl"/> diff --git a/composer.json b/composer.json index 293cb06ef403c..bcd0c7a03cbdf 100644 --- a/composer.json +++ b/composer.json @@ -147,6 +147,7 @@ "magento/module-currency-symbol": "*", "magento/module-customer": "*", "magento/module-customer-analytics": "*", + "magento/module-customer-downloadable-graph-ql": "*", "magento/module-customer-import-export": "*", "magento/module-deploy": "*", "magento/module-developer": "*", diff --git a/composer.lock b/composer.lock index f67eb50675314..bc426a3729b43 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "a4299e3f4f0d4dd4915f37a5dde8e2ed", + "content-hash": "ab7dfb217c80189b2f101f020085f221", "packages": [ { "name": "braintree/braintree_php", @@ -1552,28 +1552,28 @@ "authors": [ { "name": "Jim Wigginton", - "role": "Lead Developer", - "email": "terrafrost@php.net" + "email": "terrafrost@php.net", + "role": "Lead Developer" }, { "name": "Patrick Monnerat", - "role": "Developer", - "email": "pm@datasphere.ch" + "email": "pm@datasphere.ch", + "role": "Developer" }, { "name": "Andreas Fischer", - "role": "Developer", - "email": "bantu@phpbb.com" + "email": "bantu@phpbb.com", + "role": "Developer" }, { "name": "Hans-Jürgen Petrich", - "role": "Developer", - "email": "petrich@tronic-media.com" + "email": "petrich@tronic-media.com", + "role": "Developer" }, { "name": "Graham Campbell", - "role": "Developer", - "email": "graham@alt-three.com" + "email": "graham@alt-three.com", + "role": "Developer" } ], "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", @@ -2402,7 +2402,7 @@ }, { "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "email": "backendtea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", From cd1a93cd021f0621276ab62c4df44968b50144d3 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 26 Aug 2019 23:16:51 +0700 Subject: [PATCH 415/841] Resolve No validate number when create Website, Store View issue24293 --- .../Magento/Backend/Block/System/Store/Edit/Form/Store.php | 4 ++++ .../Magento/Backend/Block/System/Store/Edit/Form/Website.php | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php index dfbf60e1524ff..fee756b5d66be 100644 --- a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php +++ b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Backend\Block\System\Store\Edit\Form; /** @@ -129,6 +132,7 @@ protected function _prepareStoreFieldset(\Magento\Framework\Data\Form $form) 'label' => __('Sort Order'), 'value' => $storeModel->getSortOrder(), 'required' => false, + 'class' => 'validate-number validate-zero-or-greater', 'disabled' => $storeModel->isReadOnly() ] ); diff --git a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php index ec4e2cd8b444c..545620d99c4c5 100644 --- a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php +++ b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Backend\Block\System\Store\Edit\Form; /** @@ -85,6 +88,7 @@ protected function _prepareStoreFieldset(\Magento\Framework\Data\Form $form) 'label' => __('Sort Order'), 'value' => $websiteModel->getSortOrder(), 'required' => false, + 'class' => 'validate-number validate-zero-or-greater', 'disabled' => $websiteModel->isReadOnly() ] ); From 4a3456e269feae952de09e8b4c031f07adf4fb1e Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 26 Aug 2019 23:26:28 +0700 Subject: [PATCH 416/841] Resolve All shipping method (Flat Rate, Table Rate,...) has No validate number (Sort Order) issue24295 --- app/code/Magento/Dhl/etc/adminhtml/system.xml | 1 + app/code/Magento/Fedex/etc/adminhtml/system.xml | 1 + app/code/Magento/OfflineShipping/etc/adminhtml/system.xml | 3 +++ app/code/Magento/Ups/etc/adminhtml/system.xml | 1 + app/code/Magento/Usps/etc/adminhtml/system.xml | 1 + 5 files changed, 7 insertions(+) diff --git a/app/code/Magento/Dhl/etc/adminhtml/system.xml b/app/code/Magento/Dhl/etc/adminhtml/system.xml index 597f33b579282..ea3da2ca031a5 100644 --- a/app/code/Magento/Dhl/etc/adminhtml/system.xml +++ b/app/code/Magento/Dhl/etc/adminhtml/system.xml @@ -137,6 +137,7 @@ </field> <field id="sort_order" translate="label" type="text" sortOrder="2000" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="debug" translate="label" type="select" sortOrder="1950" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Debug</label> diff --git a/app/code/Magento/Fedex/etc/adminhtml/system.xml b/app/code/Magento/Fedex/etc/adminhtml/system.xml index bbaea4023a794..fdbe10a1a352e 100644 --- a/app/code/Magento/Fedex/etc/adminhtml/system.xml +++ b/app/code/Magento/Fedex/etc/adminhtml/system.xml @@ -135,6 +135,7 @@ </field> <field id="sort_order" translate="label" type="text" sortOrder="290" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> + <validate>validate-number validate-zero-or-greater</validate> </field> </group> </section> diff --git a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml index 4db5f489aa4a2..2b29d2211b9d1 100644 --- a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml +++ b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml @@ -31,6 +31,7 @@ </field> <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> @@ -92,6 +93,7 @@ </field> <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> @@ -130,6 +132,7 @@ </field> <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> diff --git a/app/code/Magento/Ups/etc/adminhtml/system.xml b/app/code/Magento/Ups/etc/adminhtml/system.xml index 8b9dc30a0188b..f1b8b22820cba 100644 --- a/app/code/Magento/Ups/etc/adminhtml/system.xml +++ b/app/code/Magento/Ups/etc/adminhtml/system.xml @@ -97,6 +97,7 @@ </field> <field id="sort_order" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> + <validate>validate-number validate-zero-or-greater</validate> </field> <field id="title" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Title</label> diff --git a/app/code/Magento/Usps/etc/adminhtml/system.xml b/app/code/Magento/Usps/etc/adminhtml/system.xml index 0bdaf49297f05..0849572e7eb1c 100644 --- a/app/code/Magento/Usps/etc/adminhtml/system.xml +++ b/app/code/Magento/Usps/etc/adminhtml/system.xml @@ -136,6 +136,7 @@ </field> <field id="sort_order" translate="label" type="text" sortOrder="220" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Sort Order</label> + <validate>validate-number validate-zero-or-greater</validate> </field> </group> </section> From 8b2f00207dec18bc6fb22cb35fdd98850805b577 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Mon, 26 Aug 2019 11:31:28 -0500 Subject: [PATCH 417/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed regular expression to include php echo syntax --- .../static/testsuite/Magento/Test/Integrity/DependencyTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index 16ba295588b58..540fcc76349d1 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -301,7 +301,7 @@ protected function _getCleanedFileContents($fileType, $file) //Removing html $contentsWithoutHtml = ''; preg_replace_callback( - '~(<\?php\s+.*\?>)~sU', + '~(<\?(php|=)\s+.*\?>)~sU', function ($matches) use ($contents, &$contentsWithoutHtml) { $contentsWithoutHtml .= $matches[1]; return $contents; From 517788a173001c32d1239435934e0aaad8461061 Mon Sep 17 00:00:00 2001 From: Hailong Zhao <hailongzh@hotmail.com> Date: Sat, 6 Apr 2019 02:53:35 -0400 Subject: [PATCH 418/841] Handle the error properly when LESS source file is empty or having compilation error, and make the error message clear and clean. --- app/code/Magento/Deploy/Package/Package.php | 3 +++ app/code/Magento/Deploy/Service/DeployPackage.php | 3 ++- .../testsuite/Magento/Email/Model/Template/FilterTest.php | 3 ++- .../Framework/Css/PreProcessor/Adapter/Less/Processor.php | 5 ++--- .../Test/Unit/PreProcessor/Adapter/Less/ProcessorTest.php | 3 +++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Deploy/Package/Package.php b/app/code/Magento/Deploy/Package/Package.php index ef50a7f47073d..68ce6407bda73 100644 --- a/app/code/Magento/Deploy/Package/Package.php +++ b/app/code/Magento/Deploy/Package/Package.php @@ -320,7 +320,10 @@ public function getFilesByType($type) */ public function deleteFile($fileId) { + $file = $this->files[$fileId]; + $deployedFileId = $file->getDeployedFileId(); unset($this->files[$fileId]); + unset($this->map[$deployedFileId]); } /** diff --git a/app/code/Magento/Deploy/Service/DeployPackage.php b/app/code/Magento/Deploy/Service/DeployPackage.php index 52cb6c6075749..34a6b147a0551 100644 --- a/app/code/Magento/Deploy/Service/DeployPackage.php +++ b/app/code/Magento/Deploy/Service/DeployPackage.php @@ -134,9 +134,10 @@ public function deployEmulated(Package $package, array $options, $skipLogging = } catch (ContentProcessorException $exception) { $errorMessage = __('Compilation from source: ') . $file->getSourcePath() - . PHP_EOL . $exception->getMessage(); + . PHP_EOL . $exception->getMessage() . PHP_EOL; $this->errorsCount++; $this->logger->critical($errorMessage); + $package->deleteFile($file->getFileId()); } catch (\Exception $exception) { $this->logger->critical( 'Compilation from source ' . $file->getSourcePath() . ' failed' . PHP_EOL . (string)$exception diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php index 7e16115ed2fef..cce497b296957 100644 --- a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php +++ b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php @@ -9,6 +9,7 @@ use Magento\Framework\App\State; use Magento\Framework\App\TemplateTypesInterface; use Magento\Framework\Phrase; +use Magento\Framework\View\Asset\ContentProcessorInterface; use Magento\Setup\Module\I18n\Locale; use Magento\Theme\Block\Html\Footer; @@ -267,7 +268,7 @@ public function cssDirectiveDataProvider() 'Empty or missing file' => [ TemplateTypesInterface::TYPE_HTML, 'file="css/non-existent-file.css"', - '/* Contents of the specified CSS file could not be loaded or is empty */' + '/*' . PHP_EOL . ContentProcessorInterface::ERROR_MESSAGE_PREFIX . 'LESS file is empty: ', ], 'File with compilation error results in error message' => [ TemplateTypesInterface::TYPE_HTML, diff --git a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php index 517c2f5c55b90..99f3d47c863c7 100644 --- a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php +++ b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php @@ -77,7 +77,7 @@ public function processContent(File $asset) $content = $this->assetSource->getContent($asset); if (trim($content) === '') { - return ''; + throw new ContentProcessorException(new Phrase(ContentProcessorInterface::ERROR_MESSAGE_PREFIX . 'LESS file is empty: ' . $path)); } $tmpFilePath = $this->temporaryFile->createFile($path, $content); @@ -88,8 +88,7 @@ public function processContent(File $asset) gc_enable(); if (trim($content) === '') { - $this->logger->warning('Parsed less file is empty: ' . $path); - return ''; + throw new ContentProcessorException(new Phrase(ContentProcessorInterface::ERROR_MESSAGE_PREFIX . 'LESS file is empty: ' . $path)); } else { return $content; } diff --git a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Adapter/Less/ProcessorTest.php b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Adapter/Less/ProcessorTest.php index e1588db4ba97c..7c3fc799c3996 100644 --- a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Adapter/Less/ProcessorTest.php +++ b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Adapter/Less/ProcessorTest.php @@ -111,6 +111,9 @@ public function testProcessContentException() /** * Test for processContent method (empty content) + * + * @expectedException \Magento\Framework\View\Asset\ContentProcessorException + * @expectedExceptionMessageRegExp (Compilation from source: LESS file is empty: test-path) */ public function testProcessContentEmpty() { From ddf16cd170891d2ec9e29c31f9d019a526450c6b Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Mon, 26 Aug 2019 12:22:05 -0500 Subject: [PATCH 419/841] MC-18945: Reading deprecated annotation in schema - Fixed the review comments on PR --- .../GraphQl/IntrospectionQueryTest.php | 135 +++ ...ema_response_sdl_deprecated_annotation.php | 88 ++ .../GraphQl/Config/GraphQlReaderTest.php | 154 ---- .../Framework/GraphQl/_files/schemaE.graphqls | 25 - ...ema_response_sdl_deprecated_annotation.php | 856 ------------------ .../MetaReader/DeprecatedAnnotationReader.php | 6 +- .../MetaReader/FieldMetaReader.php | 28 +- 7 files changed, 248 insertions(+), 1044 deletions(-) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/_files/schema_response_sdl_deprecated_annotation.php delete mode 100644 dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls delete mode 100644 dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php index 60acb3a7a4d44..9cfe3140efcf5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php @@ -56,4 +56,139 @@ public function testIntrospectionQuery() $this->assertArrayHasKey('__schema', $this->graphQlQuery($query)); } + + /** + * Tests that Introspection Query with deprecated annotations on enum values, fields are read. + */ + public function testIntrospectionQueryWithDeprecatedAnnotationOnEnumAndFieldValues() + { + $query + = <<<QUERY + query IntrospectionQuery { + __schema { + queryType { name } + mutationType { name } + types { + ...FullType + } + } + } + fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + } + +QUERY; + + $this->assertArrayHasKey('__schema', $this->graphQlQuery($query)); + $response = $this->graphQlQuery($query); + $schemaResponseFields = $response['__schema']['types']; + $enumValueReasonArray = $this->getEnumValueDeprecatedReason($schemaResponseFields); + $fieldsValueReasonArray = $this->getFieldsValueDeprecatedReason($schemaResponseFields); + $expectedOutput = require __DIR__ . '/_files/schema_response_sdl_deprecated_annotation.php'; + + // checking field values deprecated reason + if (!empty($fieldsValueReasonArray)) { + $fieldDeprecatedReason = []; + $fieldsArray = $expectedOutput[0]['fields']; + foreach ($fieldsArray as $field) { + if ($field['isDeprecated'] === true) { + $fieldDeprecatedReason [] = $field['deprecationReason']; + } + } + $this->assertNotEmpty($fieldDeprecatedReason); + $this->assertContains( + 'Symbol was missed. Use `default_display_currency_code`.', + $fieldDeprecatedReason + ); + + $this->assertContains( + 'Symbol was missed. Use `default_display_currency_code`.', + $fieldsValueReasonArray + ); + + $this->assertNotEmpty( + array_intersect($fieldDeprecatedReason, $fieldsValueReasonArray) + ); + + } + + // checking enum values deprecated reason + if (!empty($enumValueReasonArray)) { + $enumValueDeprecatedReason = []; + $enumValuesArray = $expectedOutput[1]['enumValues']; + foreach ($enumValuesArray as $enumValue) { + if ($enumValue['isDeprecated'] === true) { + $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; + } + } + $this->assertNotEmpty($enumValueDeprecatedReason); + $this->assertContains( + '`sample_url` serves to get the downloadable sample', + $enumValueDeprecatedReason + ); + $this->assertContains( + '`sample_url` serves to get the downloadable sample', + $enumValueReasonArray + ); + $this->assertNotEmpty( + array_intersect($enumValueDeprecatedReason, $enumValueReasonArray) + ); + } + } + + /** + * Get the enum values deprecated reasons from the schema + * + * @param array $schemaResponseFields + * @return array + */ + private function getEnumValueDeprecatedReason($schemaResponseFields): array + { + $enumValueReasonArray = []; + foreach ($schemaResponseFields as $schemaResponseField) { + if (!empty($schemaResponseField['enumValues'])) { + foreach ($schemaResponseField['enumValues'] as $enumValueDeprecationReasonArray) { + if (!empty($enumValueDeprecationReasonArray['deprecationReason'])) { + $enumValueReasonArray[] = $enumValueDeprecationReasonArray['deprecationReason']; + } + } + } + } + return $enumValueReasonArray; + } + + /** + * Get the fields values deprecated reasons from the schema + * + * @param array $schemaResponseFields + * @return array + */ + private function getFieldsValueDeprecatedReason($schemaResponseFields): array + { + $fieldsValueReasonArray = []; + foreach ($schemaResponseFields as $schemaResponseField) { + if (!empty($schemaResponseField['fields'])) { + foreach ($schemaResponseField['fields'] as $fieldsValueDeprecatedReasonArray) { + if (!empty($fieldsValueDeprecatedReasonArray['deprecationReason'])) { + $fieldsValueReasonArray[] = $fieldsValueDeprecatedReasonArray['deprecationReason']; + } + } + } + } + return $fieldsValueReasonArray; + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/_files/schema_response_sdl_deprecated_annotation.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/_files/schema_response_sdl_deprecated_annotation.php new file mode 100644 index 0000000000000..c3a150fcd75ed --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/_files/schema_response_sdl_deprecated_annotation.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + [ + 'kind'=> 'OBJECT', + 'name'=> 'Currency', + 'description'=> '', + 'fields'=> [ + [ + 'name'=> 'available_currency_codes', + 'description'=> null, + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'base_currency_code', + 'description'=> null, + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + + [ + 'name'=> 'base_currency_symbol', + 'description'=> null, + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'default_display_currecy_code', + 'description'=> null, + 'isDeprecated'=> true, + 'deprecationReason'=> 'Symbol was missed. Use `default_display_currency_code`.' + ], + [ + 'name'=> 'default_display_currecy_symbol', + 'description'=> null, + 'isDeprecated'=> true, + 'deprecationReason'=> 'Symbol was missed. Use `default_display_currency_code`.' + ], + [ + 'name'=> 'default_display_currency_code', + 'description'=> null, + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'default_display_currency_symbol', + 'description'=> null, + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + [ + 'name'=> 'exchange_rates', + 'description'=> null, + 'isDeprecated'=> false, + 'deprecationReason'=> null + ], + + ], + 'enumValues'=> null + ], + [ + 'kind' => 'ENUM', + 'name' => 'DownloadableFileTypeEnum', + 'description' => '', + 'fields' => null, + 'enumValues' => [ + [ + 'name' => 'FILE', + 'description' => '', + 'isDeprecated' => true, + 'deprecationReason' => 'sample_url` serves to get the downloadable sample' + ], + [ + 'name' => 'URL', + 'description' => '', + 'isDeprecated' => true, + 'deprecationReason' => '`sample_url` serves to get the downloadable sample' + ] + ], + 'possibleTypes' => null + ], +]; diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php index 8ea142cc73a9b..2ffce44b32cfe 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php @@ -244,158 +244,4 @@ function ($a, $b) { ) ); } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testDispatchIntrospectionWithDeprecatedSDL() - { - $query - = <<<QUERY - query IntrospectionQuery { - __schema { - queryType { name } - types { - ...FullType - } - } -} - -fragment FullType on __Type { - kind - name - description - fields(includeDeprecated: true) { - name - description - args { - ...InputValue - } - type { - ...TypeRef - } - isDeprecated - deprecationReason - } - inputFields { - ...InputValue - } - interfaces { - ...TypeRef - } - enumValues(includeDeprecated: true) { - name - description - isDeprecated - deprecationReason - } - possibleTypes { - ...TypeRef - } -} - -fragment InputValue on __InputValue { - name - description - type { ...TypeRef } - defaultValue -} - -fragment TypeRef on __Type { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - } - } - } - } - } - } - } -} - - -QUERY; - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var Cache $cache */ - $cache = $this->objectManager->get(Cache::class); - $cache->clean(); - $fileResolverMock = $this->getMockBuilder( - \Magento\Framework\Config\FileResolverInterface::class - )->disableOriginalConstructor()->getMock(); - $fileList = [ - file_get_contents(__DIR__ . '/../_files/schemaE.graphqls') - ]; - $fileResolverMock->expects($this->any())->method('get')->will($this->returnValue($fileList)); - - $postData = [ - 'query' => $query, - 'variables' => null, - 'operationName' => 'IntrospectionQuery' - ]; - /** @var Http $request */ - $request = $this->objectManager->get(Http::class); - $request->setPathInfo('/graphql'); - $request->setMethod('POST'); - $request->setContent(json_encode($postData)); - $headers = $this->objectManager->create(\Zend\Http\Headers::class) - ->addHeaders(['Content-Type' => 'application/json']); - $request->setHeaders($headers); - - $response = $this->graphQlController->dispatch($request); - $this->jsonSerializer->unserialize($response->getContent()); - $expectedOutput = require __DIR__ . '/../_files/schema_response_sdl_deprecated_annotation.php'; - - //Checks to make sure that the given description exists in the expectedOutput array - - $this->assertTrue( - array_key_exists( - array_search( - 'Comment for SortEnum', - array_column($expectedOutput, 'description') - ), - $expectedOutput - ) - ); - - //Checks to make sure that the given deprecatedReason exists in the expectedOutput array for enumValues, fields. - $fieldsArray = $expectedOutput[0]['fields']; - $enumValuesArray = $expectedOutput[1]['enumValues']; - - foreach ($fieldsArray as $field) { - if ($field['isDeprecated'] === true) { - $typeDeprecatedReason [] = $field['deprecationReason']; - } - } - $this->assertNotEmpty($typeDeprecatedReason); - $this->assertContains('Deprecated url_path test', $typeDeprecatedReason); - - foreach ($enumValuesArray as $enumValue) { - if ($enumValue['isDeprecated'] === true) { - $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; - } - } - $this->assertNotEmpty($enumValueDeprecatedReason); - $this->assertContains('Deprecated SortEnum Value test', $enumValueDeprecatedReason); - } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls deleted file mode 100644 index 0fd1a011f3584..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schemaE.graphqls +++ /dev/null @@ -1,25 +0,0 @@ -type Query { - placeholder: String @doc(description: "comment for placeholder.") -} - -type SimpleProduct { - url_key: String @doc(description: "comment for url_key for simple product that implements [ProductInterface]") - url_path: String @deprecated(reason:"Deprecated url_path test") -} - -enum SortEnum @doc(description: "Comment for SortEnum.") -{ - ASC @doc(description:"Ascending Order") - DESC @deprecated(reason:"Deprecated SortEnum Value test") -} - -type SearchResultPageInfo @doc(description:"Comment for SearchResultPageInfo") -{ - page_size: Int @doc(description:"Comment for page_size") - current_page: Int @doc(description:"Comment for current_page") -} - -interface ProductInterface { - url_key: String @doc(description: "comment for url_key inside ProductInterface type.") - url_path: String -} diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php deleted file mode 100644 index ea4b290f1edc8..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/_files/schema_response_sdl_deprecated_annotation.php +++ /dev/null @@ -1,856 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -return [ - [ - 'kind'=> 'OBJECT', - 'name'=> 'SimpleProduct', - 'description'=> 'Comment for empty SimpleProduct type', - 'fields'=> [ - [ - 'name'=> 'options', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'INTERFACE', - 'name'=> 'CustomizableOptionInterface', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'url_key', - 'description'=> 'comment for url_key for simple product that implements [ProductInterface]', - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'url_path', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> true, - 'deprecationReason'=> 'Deprecated url_path test' - ], - [ - 'name'=> 'id', - 'description'=> 'comment for [ProductInterface].', - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'name', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'sku', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'special_price', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Float', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'special_from_date', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'attribute_set_id', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'tier_price', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Float', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'category_ids', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'updated_at', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'country_of_manufacture', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'type_id', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'website_ids', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'category_links', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'ProductCategoryLinks', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'product_links', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'INTERFACE', - 'name'=> 'ProductLinksInterface', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'media_gallery_entries', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'MediaGalleryEntry', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'tier_prices', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'ProductTierPrices', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'price', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'ProductPrices', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'manufacturer', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ] - ], - 'inputFields'=> null, - 'interfaces'=> [ - [ - 'kind'=> 'INTERFACE', - 'name'=> 'ProductInterface', - 'ofType'=> null - ], - [ - 'kind'=> 'INTERFACE', - 'name'=> 'PhysicalProductInterface', - 'ofType'=> null - ], - [ - 'kind'=> 'INTERFACE', - 'name'=> 'CustomizableProductInterface', - 'ofType'=> null - ] - ], - 'enumValues'=> null, - 'possibleTypes'=> null - ], - [ - 'kind' => 'ENUM', - 'name' => 'SortEnum', - 'description' => 'Comment for SortEnum', - 'fields' => null, - 'inputFields' => null, - 'interfaces' => null, - 'enumValues' => [ - [ - 'name' => 'ASC', - 'description' => 'Ascending Order', - 'isDeprecated' => false, - 'deprecationReason' => '' - ], - [ - 'name' => 'DESC', - 'description' => '', - 'isDeprecated' => true, - 'deprecationReason' => 'Deprecated SortEnum Value test' - ] - ], - 'possibleTypes' => null - ], - [ - 'kind' => 'OBJECT', - 'name' => 'Query', - 'description' => null, - 'fields' => [ - [ - 'name' => 'placeholder', - 'description' => 'comment for placeholder.', - 'args' => [ - - ], - 'type' => [ - 'kind' => 'SCALAR', - 'name' => 'String', - 'ofType' => null - ], - 'isDeprecated' => false, - 'deprecationReason' => null - ], - [ - 'name' => 'products', - 'description' => 'comment for products fields', - 'args' => [ - [ - 'name' => 'search', - 'description' => '', - 'type' => [ - 'kind' => 'SCALAR', - 'name' => 'String', - 'ofType' => null - ], - 'defaultValue' => null - ], - [ - 'name' => 'filter', - 'description' => '', - 'type' => [ - 'kind' => 'INPUT_OBJECT', - 'name' => 'ProductFilterInput', - 'ofType' => null - ], - 'defaultValue' => null - ], - [ - 'name' => 'pageSize', - 'description' => '', - 'type' => [ - 'kind' => 'SCALAR', - 'name' => 'Int', - 'ofType' => null - ], - 'defaultValue' => null - ], - [ - 'name' => 'currentPage', - 'description' => '', - 'type' => [ - 'kind' => 'SCALAR', - 'name' => 'Int', - 'ofType' => null - ], - 'defaultValue' => null - ], - [ - 'name' => 'sort', - 'description' => '', - 'type' => [ - 'kind' => 'INPUT_OBJECT', - 'name' => 'ProductSortInput', - 'ofType' => null - ], - 'defaultValue' => null - ] - ], - 'type' => [ - 'kind' => 'OBJECT', - 'name' => 'Products', - 'ofType' => null - ], - 'isDeprecated' => false, - 'deprecationReason' => null - ] - ], - 'inputFields' => null, - 'interfaces' => [ - - ], - 'enumValues' => null, - 'possibleTypes' => null - ], - [ - 'kind' => 'OBJECT', - 'name' => 'Products', - 'description' => 'Comment for Products', - 'fields' => [ - [ - 'name' => 'items', - 'description' => 'comment for items[Products].', - 'args' => [ - - ], - 'type' => [ - 'kind' => 'LIST', - 'name' => null, - 'ofType' => [ - 'kind' => 'INTERFACE', - 'name' => 'ProductInterface', - 'ofType' => null - ] - ], - 'isDeprecated' => false, - 'deprecationReason' => null - ], - [ - 'name' => 'page_info', - 'description' => 'comment for page_info.', - 'args' => [ - - ], - 'type' => [ - 'kind' => 'OBJECT', - 'name' => 'SearchResultPageInfo', - 'ofType' => null - ], - 'isDeprecated' => false, - 'deprecationReason' => null - ], - [ - 'name' => 'total_count', - 'description' => null, - 'args' => [ - - ], - 'type' => [ - 'kind' => 'SCALAR', - 'name' => 'Int', - 'ofType' => null - ], - 'isDeprecated' => false, - 'deprecationReason' => null - ] - ], - 'inputFields' => null, - 'interfaces' => [ - - ], - 'enumValues' => null, - 'possibleTypes' => null - ], - [ - 'kind'=> 'INTERFACE', - 'name'=> 'ProductInterface', - 'description'=> 'comment for ProductInterface', - 'fields'=> [ - [ - 'name'=> 'url_key', - 'description'=> 'comment for url_key inside ProductInterface type.', - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'url_path', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'id', - 'description'=> 'comment for [ProductInterface].', - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'name', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'sku', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'special_price', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Float', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'special_from_date', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'attribute_set_id', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'tier_price', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Float', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'category_ids', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'updated_at', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'country_of_manufacture', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'type_id', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'String', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'website_ids', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'category_links', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'ProductCategoryLinks', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'product_links', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'INTERFACE', - 'name'=> 'ProductLinksInterface', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'media_gallery_entries', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'MediaGalleryEntry', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'tier_prices', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'LIST', - 'name'=> null, - 'ofType'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'ProductTierPrices', - 'ofType'=> null - ] - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'price', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'OBJECT', - 'name'=> 'ProductPrices', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ], - [ - 'name'=> 'manufacturer', - 'description'=> null, - 'args'=> [ - - ], - 'type'=> [ - 'kind'=> 'SCALAR', - 'name'=> 'Int', - 'ofType'=> null - ], - 'isDeprecated'=> false, - 'deprecationReason'=> null - ] - ], - 'inputFields'=> null, - 'interfaces'=> null, - 'enumValues'=> null, - 'possibleTypes'=> [ - [ - 'kind'=> 'OBJECT', - 'name'=> 'SimpleProduct', - 'ofType'=> null - ], - [ - 'kind'=> 'OBJECT', - 'name'=> 'VirtualProduct', - 'ofType'=> null - ] - ] - ], -]; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php index dbbe83bf9fd43..922ae87ecf449 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php @@ -20,16 +20,16 @@ class DeprecatedAnnotationReader */ public function read(\GraphQL\Language\AST\NodeList $directives) : array { - $argMap = []; + $argumentsMap = []; foreach ($directives as $directive) { if ($directive->name->value == 'deprecated') { foreach ($directive->arguments as $directiveArgument) { if ($directiveArgument->name->value == 'reason') { - $argMap = ["reason" => $directiveArgument->value->value]; + $argumentsMap = ["reason" => $directiveArgument->value->value]; } } } } - return $argMap; + return $argumentsMap; } } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index c92dcf39460a8..e2ac1fdca7b04 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -99,12 +99,7 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra $result['arguments'][$argumentName]['defaultValue'] = $argumentMeta->defaultValue; } $typeMeta = $argumentMeta->getType(); - - // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge - $result['arguments'][$argumentName] = array_merge( - $result['arguments'][$argumentName], - $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) - ); + $result['arguments'][$argumentName] = $this->argumentMetaType($typeMeta, $argumentMeta); if ($this->docReader->read($argumentMeta->astNode->directives)) { $result['arguments'][$argumentName]['description'] = @@ -119,6 +114,27 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra return $result; } + /** + * Get the argumentMetaType result array + * + * @param array $typeMeta + * @param array $argumentMeta + * @return array + */ + private function argumentMetaType($typeMeta, $argumentMeta) + { + $argumentName = $argumentMeta->name; + $result['arguments'][$argumentName] = [ + 'name' => $argumentName, + ]; + $result['arguments'][$argumentName] = array_merge( + $result['arguments'][$argumentName], + $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) + ); + + return $result['arguments'][$argumentName]; + } + /** * Read resolver if an annotation with the class of the resolver is defined in the meta * From c6fa6ff27fa9b39237766692af9c10911cbf9700 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin <dyushkin@adobe.com> Date: Mon, 26 Aug 2019 13:34:51 -0500 Subject: [PATCH 420/841] MC-19114: Authorize.net "Qty to Refund" not editable --- .../Gateway/Command/RefundTransactionStrategyCommand.php | 4 +++- .../Gateway/Command/RefundTransactionStrategyCommandTest.php | 2 +- app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php index 74f01494d82bf..3cdfcf23ba607 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php @@ -90,7 +90,9 @@ private function canVoid(array $details, array $commandSubject) :bool { if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') { if ((float) $details['transaction']['authAmount'] !== (float) $commandSubject['amount']) { - throw new CommandException(__('Transaction has not been settled yet, partial void is not available.')); + throw new CommandException( + __('The transaction has not been settled, a partial refund is not yet available.') + ); } return true; diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php index 9fad69c899a3f..79477b06e0e6c 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php @@ -100,7 +100,7 @@ public function testCommandWillVoidWhenTransactionIsPendingSettlement() /** * @expectedException \Magento\Payment\Gateway\Command\CommandException - * @expectedExceptionMessage Transaction has not been settled yet, partial void is not available. + * @expectedExceptionMessage The transaction has not been settled, a partial refund is not yet available. */ public function testCommandWillThrowExceptionWhenVoidTransactionIsPartial() { diff --git a/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv index c6c2b0d6f1fb2..3c5b677c88cc8 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv +++ b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv @@ -19,4 +19,4 @@ Authorize.net,Authorize.net "ccLast4","Last 4 Digits of Card" "There was an error while trying to process the refund.","There was an error while trying to process the refund." "This transaction cannot be refunded with its current status.","This transaction cannot be refunded with its current status." -"Transaction has not been settled yet, partial void is not available.","Transaction has not been settled yet, partial void is not available." +"The transaction has not been settled, a partial refund is not yet available.","The transaction has not been settled, a partial refund is not yet available." From 9d7ba9c3d636832bd878cb2ca34b5b6bd8f0a58f Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Mon, 26 Aug 2019 13:37:15 -0500 Subject: [PATCH 421/841] MC-18945: Reading deprecated annotation in schema - Fixed the reader field metareader file --- .../GraphQlReader/MetaReader/FieldMetaReader.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index e2ac1fdca7b04..c147cf4d1c8ed 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -117,12 +117,14 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra /** * Get the argumentMetaType result array * - * @param array $typeMeta - * @param array $argumentMeta + * @param array $typeMeta + * @param array $argumentMeta * @return array */ - private function argumentMetaType($typeMeta, $argumentMeta) - { + private function argumentMetaType( + \GraphQL\Type\Definition\InputType $typeMeta, + \GraphQL\Type\Definition\FieldArgument $argumentMeta + ) { $argumentName = $argumentMeta->name; $result['arguments'][$argumentName] = [ 'name' => $argumentName, From bb996868319ae8efa47088c9f8f2b217c4727819 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 26 Aug 2019 13:59:22 -0500 Subject: [PATCH 422/841] MC-19441: Fix web api test failures - fix fixture and tests --- .../GraphQl/Catalog/ProductSearchTest.php | 286 ++++++------------ ..._attribute_layered_navigation_rollback.php | 11 - 2 files changed, 100 insertions(+), 197 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 6356f7b44db84..cd7239d808b41 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -94,6 +94,73 @@ public function testFilterLn() ); } + /** + * Layered navigation for Configurable products with out of stock options + * Two configurable products each having two variations and one of the child products of one Configurable set to OOS + * + * @magentoApiDataFixture Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testLayeredNavigationWithConfigurableChildrenOutOfStock() + { + CacheCleaner::cleanAll(); + $attributeCode = 'test_configurable'; + /** @var \Magento\Eav\Model\Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); + /** @var AttributeOptionInterface[] $options */ + $options = $attribute->getOptions(); + array_shift($options); + $firstOption = $options[0]->getValue(); + $secondOption = $options[1]->getValue(); + $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); + $response = $this->graphQlQuery($query); + + // 1 product is returned since only one child product with attribute option1 from 1st Configurable product is OOS + $this->assertEquals(1, $response['products']['total_count']); + + // Custom attribute filter layer data + $this->assertResponseFields( + $response['products']['filters'][1], + [ + 'name' => $attribute->getDefaultFrontendLabel(), + 'request_var'=> $attribute->getAttributeCode(), + 'filter_items_count'=> 2, + 'filter_items' => [ + [ + 'label' => 'Option 1', + 'items_count' => 1, + 'value_string' => $firstOption, + '__typename' =>'LayerFilterItem' + ], + [ + 'label' => 'Option 2', + 'items_count' => 1, + 'value_string' => $secondOption, + '__typename' =>'LayerFilterItem' + ] + ], + ] + ); + + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $outOfStockChildProduct = $productRepository->get('simple_30'); + // All child variations with this attribute are now set to Out of Stock + $outOfStockChildProduct->setStockData( + ['use_config_manage_stock' => 1, + 'qty' => 0, + 'is_qty_decimal' => 0, + 'is_in_stock' => 0] + ); + $productRepository->save($outOfStockChildProduct); + $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); + $response = $this->graphQlQuery($query); + $this->assertEquals(0, $response['products']['total_count']); + $this->assertEmpty($response['products']['items']); + $this->assertEmpty($response['products']['filters']); + } + /** * Filter products using custom attribute of input type select(dropdown) and filterTypeInput eq * @@ -373,7 +440,7 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() } /** - * Filter by category_id and custom attribute + * Filter by single category and custom attribute * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -507,72 +574,6 @@ private function getQueryProductsWithCustomAttribute($attributeCode, $optionValu QUERY; } - /** - * Layered navigation for Configurable products with out of stock options - * Two configurable products each having two variations and one of the child products of one Configurable set to OOS - * - * @magentoApiDataFixture Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testLayeredNavigationWithConfigurableChildrenOutOfStock() - { - $attributeCode = 'test_configurable'; - /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); - $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); - /** @var AttributeOptionInterface[] $options */ - $options = $attribute->getOptions(); - array_shift($options); - $firstOption = $options[0]->getValue(); - $secondOption = $options[1]->getValue(); - $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); - $response = $this->graphQlQuery($query); - - //1 product will be returned since only one child product with attribute option1 from 1st Configurable product is OOS - $this->assertEquals(1, $response['products']['total_count']); - - // Custom attribute filter layer data - $this->assertResponseFields( - $response['products']['filters'][1], - [ - 'name' => $attribute->getDefaultFrontendLabel(), - 'request_var'=> $attribute->getAttributeCode(), - 'filter_items_count'=> 2, - 'filter_items' => [ - [ - 'label' => 'Option 1', - 'items_count' => 1, - 'value_string' => $firstOption, - '__typename' =>'LayerFilterItem' - ], - [ - 'label' => 'Option 2', - 'items_count' => 1, - 'value_string' => $secondOption, - '__typename' =>'LayerFilterItem' - ] - ], - ] - ); - - /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $outOfStockChildProduct = $productRepository->get('simple_30'); - // All child variations with this attribute are now set to Out of Stock - $outOfStockChildProduct->setStockData( - ['use_config_manage_stock' => 1, - 'qty' => 0, - 'is_qty_decimal' => 0, - 'is_in_stock' => 0] - ); - $productRepository->save($outOfStockChildProduct); - $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); - $response = $this->graphQlQuery($query); - $this->assertEquals(0, $response['products']['total_count']); - $this->assertEmpty($response['products']['items']); - $this->assertEmpty($response['products']['filters']); - } - /** * Get array with expected data for layered navigation filters * @@ -672,12 +673,9 @@ public function testFilterProductsWithinSpecificPriceRangeSortedByNameDesc() products( filter: { - price:{gt: "5", lt: "50"} - or: - { - sku:{like:"simple%"} - name:{like:"Simple%"} - } + price:{from: "5", to: "50"} + sku:{like:"simple%"} + name:{like:"Simple%"} } pageSize:4 currentPage:1 @@ -729,79 +727,6 @@ public function testFilterProductsWithinSpecificPriceRangeSortedByNameDesc() $this->assertEquals(4, $response['products']['page_info']['page_size']); } - /** - * Test a visible product with matching sku or name with special price - * - * Requesting for items that has a special price and price < $60, that are visible in Catalog, Search or Both which - * either has a sku like “simple” or name like “configurable”sorted by price in DESC - * - * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testFilterVisibleProductsWithMatchingSkuOrNameWithSpecialPrice() - { - $query - = <<<QUERY -{ - products( - filter: - { - special_price:{neq:"null"} - price:{lt:"60"} - or: - { - sku:{like:"%simple%"} - name:{like:"%configurable%"} - } - weight:{eq:"1"} - } - pageSize:6 - currentPage:1 - sort: - { - price:DESC - } - ) - { - items - { - sku - price { - minimalPrice { - amount { - value - currency - } - } - } - name - ... on PhysicalProductInterface { - weight - } - type_id - attribute_set_id - } - total_count - page_info - { - page_size - current_page - } - } -} -QUERY; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $product1 = $productRepository->get('simple1'); - $product2 = $productRepository->get('simple2'); - $filteredProducts = [$product2, $product1]; - - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('total_count', $response['products']); - $this->assertEquals(2, $response['products']['total_count']); - $this->assertProductItems($filteredProducts, $response); - } - /** * pageSize = total_count and current page = 2 * expected - error is thrown @@ -813,7 +738,7 @@ public function testFilterVisibleProductsWithMatchingSkuOrNameWithSpecialPrice() public function testSearchWithFilterWithPageSizeEqualTotalCount() { - CacheCleaner::cleanAll(); + $query = <<<QUERY { @@ -867,12 +792,12 @@ public function testSearchWithFilterWithPageSizeEqualTotalCount() } /** - * Requesting for items that match a specific SKU or NAME within a certain price range sorted by Price in ASC order + * Filtering for products and sorting using multiple sort parameters * * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testQueryProductsInCurrentPageSortedByPriceASC() + public function testQueryProductsInCurrentPageSortedByMultipleSortParameters() { $query = <<<QUERY @@ -961,7 +886,7 @@ public function testQueryProductsInCurrentPageSortedByPriceASC() */ public function testQueryProductsSortedByNameASC() { - CacheCleaner::cleanAll(); + $query = <<<QUERY { @@ -1010,56 +935,45 @@ public function testQueryProductsSortedByNameASC() } /** - * @magentoApiDataFixture Magento/Catalog/_files/product_in_multiple_categories.php + * @magentoApiDataFixture Magento/Catalog/_files/categories.php */ - public function testFilteringForProductInMultipleCategories() + public function testFilteringForProductsFromMultipleCategories() { - $productSku = 'simple333'; $query = <<<QUERY { - products(filter:{sku:{eq:"{$productSku}"}}) + products(filter:{ + category_id :{in:["4","5","12"]} + }) { - items{ - id - sku - name - attribute_set_id - categories { - id + items + { + sku + name + } + total_count + filters{ + request_var + name + filter_items_count + filter_items{ + value_string + label + } + } } - } - } } QUERY; $response = $this->graphQlQuery($query); /** @var ProductRepositoryInterface $productRepository */ - $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - /** @var ProductInterface $product */ - $product = $productRepository->get('simple333'); - $categoryIds = $product->getCategoryIds(); - foreach ($categoryIds as $index => $value) { - $categoryIds[$index] = [ 'id' => (int)$value]; - } - $this->assertNotEmpty($response['products']['items'][0]['categories'], "Categories must not be empty"); - $this->assertNotNull($response['products']['items'][0]['categories'], "categories must not be null"); - $this->assertEquals($categoryIds, $response['products']['items'][0]['categories']); - /** @var MetadataPool $metaData */ - $metaData = ObjectManager::getInstance()->get(MetadataPool::class); - $linkField = $metaData->getMetadata(ProductInterface::class)->getLinkField(); - $assertionMap = [ - - ['response_field' => 'id', 'expected_value' => $product->getData($linkField)], - ['response_field' => 'sku', 'expected_value' => $product->getSku()], - ['response_field' => 'name', 'expected_value' => $product->getName()], - ['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()] - ]; - $this->assertResponseFields($response['products']['items'][0], $assertionMap); + $this->assertEquals(3, $response['products']['total_count']); } /** + * Filter products by category only + * * @magentoApiDataFixture Magento/Catalog/_files/product_in_multiple_categories.php * @return void */ @@ -1150,7 +1064,7 @@ public function testFilterProductsBySingleCategoryId() */ public function testQuerySortByPriceDESCWithDefaultPageSize() { - CacheCleaner::cleanAll(); + $query = <<<QUERY { @@ -1270,7 +1184,7 @@ public function testProductBasicFullTextSearchQuery() /** * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php */ - public function testProductsThatMatchWithinASpecificPriceRange() + public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() { $query =<<<QUERY diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php index 2e4227eb35392..49e2a8e88a1ac 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation_rollback.php @@ -7,14 +7,3 @@ // phpcs:ignore Magento2.Security.IncludeFile require __DIR__ . '/../../ConfigurableProduct/_files/configurable_products_rollback.php'; - -use Magento\TestFramework\Helper\Bootstrap; -use Magento\Eav\Api\AttributeRepositoryInterface; - -$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); - -/** @var AttributeRepositoryInterface $attributeRepository */ -$attributeRepository = Bootstrap::getObjectManager()->get(AttributeRepositoryInterface::class); -/** @var \Magento\Eav\Api\Data\AttributeInterface $attribute */ -$attribute = $attributeRepository->get('catalog_product', 'test_configurable'); -$attributeRepository->delete($attribute); From 797e05e66abd8340e44c849e2a503d88b2653814 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Mon, 26 Aug 2019 15:01:02 -0500 Subject: [PATCH 423/841] MC-19538: Error Sorry we cannot connect to PayPal. Please try again in a few minutes - Fixed sending shipping amount to Braintree PayPal from shopping cart page. --- .../Braintree/Observer/AddPaypalShortcuts.php | 30 ++++++++++++++-- .../Unit/Observer/AddPaypalShortcutsTest.php | 12 +++++-- .../Magento/Braintree/etc/frontend/di.xml | 17 +++++++++ .../paypal/button_shopping_cart.phtml | 32 +++++++++++++++++ .../web/js/paypal/button_shopping_cart.js | 36 +++++++++++++++++++ 5 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 app/code/Magento/Braintree/view/frontend/templates/paypal/button_shopping_cart.phtml create mode 100644 app/code/Magento/Braintree/view/frontend/web/js/paypal/button_shopping_cart.js diff --git a/app/code/Magento/Braintree/Observer/AddPaypalShortcuts.php b/app/code/Magento/Braintree/Observer/AddPaypalShortcuts.php index 132f4b92a3e2d..ea16745a24117 100644 --- a/app/code/Magento/Braintree/Observer/AddPaypalShortcuts.php +++ b/app/code/Magento/Braintree/Observer/AddPaypalShortcuts.php @@ -15,9 +15,27 @@ class AddPaypalShortcuts implements ObserverInterface { /** - * Block class + * Alias for mini-cart block. */ - const PAYPAL_SHORTCUT_BLOCK = \Magento\Braintree\Block\Paypal\Button::class; + private const PAYPAL_MINICART_ALIAS = 'mini_cart'; + + /** + * Alias for shopping cart page. + */ + private const PAYPAL_SHOPPINGCART_ALIAS = 'shopping_cart'; + + /** + * @var string[] + */ + private $buttonBlocks; + + /** + * @param string[] $buttonBlocks + */ + public function __construct(array $buttonBlocks = []) + { + $this->buttonBlocks = $buttonBlocks; + } /** * Add Braintree PayPal shortcut buttons @@ -35,7 +53,13 @@ public function execute(Observer $observer) /** @var ShortcutButtons $shortcutButtons */ $shortcutButtons = $observer->getEvent()->getContainer(); - $shortcut = $shortcutButtons->getLayout()->createBlock(self::PAYPAL_SHORTCUT_BLOCK); + if ($observer->getData('is_shopping_cart')) { + $shortcut = $shortcutButtons->getLayout() + ->createBlock($this->buttonBlocks[self::PAYPAL_SHOPPINGCART_ALIAS]); + } else { + $shortcut = $shortcutButtons->getLayout() + ->createBlock($this->buttonBlocks[self::PAYPAL_MINICART_ALIAS]); + } $shortcutButtons->addShortcut($shortcut); } diff --git a/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php b/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php index 377b4d3c650ae..7bf1722e317ef 100644 --- a/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php @@ -19,9 +19,17 @@ */ class AddPaypalShortcutsTest extends \PHPUnit\Framework\TestCase { + /** + * Tests PayPal shortcuts observer. + */ public function testExecute() { - $addPaypalShortcuts = new AddPaypalShortcuts(); + $addPaypalShortcuts = new AddPaypalShortcuts( + [ + 'mini_cart' => 'Minicart-block', + 'shopping_cart' => 'Shoppingcart-block' + ] + ); /** @var Observer|\PHPUnit_Framework_MockObject_MockObject $observerMock */ $observerMock = $this->getMockBuilder(Observer::class) @@ -60,7 +68,7 @@ public function testExecute() $layoutMock->expects(self::once()) ->method('createBlock') - ->with(AddPaypalShortcuts::PAYPAL_SHORTCUT_BLOCK) + ->with('Minicart-block') ->willReturn($blockMock); $shortcutButtonsMock->expects(self::once()) diff --git a/app/code/Magento/Braintree/etc/frontend/di.xml b/app/code/Magento/Braintree/etc/frontend/di.xml index d8d3a93b71dc3..330fa51258c44 100644 --- a/app/code/Magento/Braintree/etc/frontend/di.xml +++ b/app/code/Magento/Braintree/etc/frontend/di.xml @@ -55,6 +55,23 @@ <argument name="payment" xsi:type="object">BraintreePayPalFacade</argument> </arguments> </type> + <virtualType name="Magento\Braintree\Block\Paypal\ButtonShoppingCartVirtual" type="Magento\Braintree\Block\Paypal\Button"> + <arguments> + <argument name="data" xsi:type="array"> + <item name="template" xsi:type="string">Magento_Braintree::paypal/button_shopping_cart.phtml</item> + <item name="alias" xsi:type="string">braintree.paypal.mini-cart</item> + <item name="button_id" xsi:type="string">braintree-paypal-mini-cart</item> + </argument> + </arguments> + </virtualType> + <type name="Magento\Braintree\Observer\AddPaypalShortcuts"> + <arguments> + <argument name="buttonBlocks" xsi:type="array"> + <item name="mini_cart" xsi:type="string">Magento\Braintree\Block\Paypal\Button</item> + <item name="shopping_cart" xsi:type="string">Magento\Braintree\Block\Paypal\ButtonShoppingCartVirtual</item> + </argument> + </arguments> + </type> <type name="Magento\Braintree\Model\Ui\PayPal\ConfigProvider"> <arguments> diff --git a/app/code/Magento/Braintree/view/frontend/templates/paypal/button_shopping_cart.phtml b/app/code/Magento/Braintree/view/frontend/templates/paypal/button_shopping_cart.phtml new file mode 100644 index 0000000000000..913e1035b910f --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/templates/paypal/button_shopping_cart.phtml @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * @var \Magento\Braintree\Block\Paypal\Button $block + */ + +$id = $block->getContainerId() . random_int(0, PHP_INT_MAX); + +$config = [ + 'Magento_Braintree/js/paypal/button_shopping_cart' => [ + 'id' => $id, + 'clientToken' => $block->getClientToken(), + 'displayName' => $block->getMerchantName(), + 'actionSuccess' => $block->getActionSuccess(), + 'environment' => $block->getEnvironment() + ] +]; + +?> +<div data-mage-init='<?= /* @noEscape */ json_encode($config); ?>' + class="paypal checkout paypal-logo braintree-paypal-logo<?= /* @noEscape */ $block->getContainerId(); ?>-container"> + <div data-currency="<?= /* @noEscape */ $block->getCurrency(); ?>" + data-locale="<?= /* @noEscape */ $block->getLocale(); ?>" + data-amount="<?= /* @noEscape */ $block->getAmount(); ?>" + id="<?= /* @noEscape */ $id; ?>" + class="action-braintree-paypal-logo"> + </div> +</div> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/paypal/button_shopping_cart.js b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button_shopping_cart.js new file mode 100644 index 0000000000000..9dd249998c152 --- /dev/null +++ b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button_shopping_cart.js @@ -0,0 +1,36 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define( + [ + 'Magento_Braintree/js/paypal/button', + 'Magento_Checkout/js/model/quote', + 'domReady!' + ], + function ( + Component, + quote + ) { + 'use strict'; + + return Component.extend({ + + /** + * Overrides amount with a value from quote. + * + * @returns {Object} + * @private + */ + getClientConfig: function (data) { + var config = this._super(data); + + if (config.amount !== quote.totals()['base_grand_total']) { + config.amount = quote.totals()['base_grand_total']; + } + + return config; + } + }); + } +); From 6730caed941cf5971f22bb7f1b151d4163622b5b Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 26 Aug 2019 17:38:51 -0500 Subject: [PATCH 424/841] MC-19441: Fix web api test failures - fix fixture and tests --- .../testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index cd7239d808b41..169aba7c15734 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -746,7 +746,7 @@ public function testSearchWithFilterWithPageSizeEqualTotalCount() search : "simple" filter: { - price:{from:"60"} + price:{from:"5.59"} } pageSize:2 currentPage:2 From 52cdae7e754ab4a12b06e9bf2df05127cb5a7a53 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Tue, 27 Aug 2019 00:35:46 +0100 Subject: [PATCH 425/841] Add changes requested during CR --- app/code/Magento/Analytics/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Analytics/README.md b/app/code/Magento/Analytics/README.md index cb78a27dae8e0..e0f1c818c7a94 100644 --- a/app/code/Magento/Analytics/README.md +++ b/app/code/Magento/Analytics/README.md @@ -4,10 +4,10 @@ The Magento_Analytics module integrates your Magento instance with the [Magento The module implements the following functionality: -- Enabling subscription to the MBI and automatic re-subscription +- Enabling subscription to Magento Business Intelligence (MBI) and automatic re-subscription - Changing the base URL with the same MBI account remained - Declaring the configuration schemas for report data collection -- Collecting the Magento instance data as reports for the MBI +- Collecting the Magento instance data as reports for MBI - Introducing API that provides the collected data - Extending Magento configuration with the module parameters: - Subscription status (enabled/disabled) From c9737d0e388b08696967bc60941b139dfd55a244 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Mon, 26 Aug 2019 18:56:28 -0500 Subject: [PATCH 426/841] change code styles --- .../Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php index 22687851a2de9..4da1443c9e05f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php @@ -37,6 +37,7 @@ public function __construct( /** * Get Root Category Id + * * @return int * @throws LocalizedException */ @@ -53,4 +54,3 @@ public function execute() return $this->rootCategoryId; } } - From 0ca4b90cab8998ea5516459c29dd1412ce3a779d Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Tue, 27 Aug 2019 01:54:00 +0100 Subject: [PATCH 427/841] Add changes requested during CR --- app/code/Magento/Authorizenet/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Authorizenet/README.md b/app/code/Magento/Authorizenet/README.md index ef022a6e236a2..6d157860e42d6 100644 --- a/app/code/Magento/Authorizenet/README.md +++ b/app/code/Magento/Authorizenet/README.md @@ -1,7 +1,6 @@ # Magento_Authorizenet module -The Magento_Authorizenet module is a part of the staging functionality in Magento EE. The module adds the “Configurations” tab and the configuration wizard to the Schedule Update form of a product. You can change the Configurable Product attributes in campaigns. These updates are shown on the campaign dashboard. - +The Magento_Authorizenet module is a part of the staging functionality in Magento Commerce. The module adds the “Configurations” tab and the configuration wizard to the Schedule Update form of a product. You can change the Configurable Product attributes in campaigns. These updates are shown on the campaign dashboard. ## Extensibility Extension developers can interact with the Magento_Authorizenet module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). @@ -22,10 +21,10 @@ This module dispatches the following events: This module observes the following events: - - `checkout_submit_all_after` event in `Magento\Authorizenet\Observer\SaveOrderAfterSubmitObserver` file. - - `checkout_directpost_placeOrder` event in `Magento\Authorizenet\Observer\AddFieldsToResponseObserver` file. + - `checkout_submit_all_after` event in the `Magento\Authorizenet\Observer\SaveOrderAfterSubmitObserver` file. + - `checkout_directpost_placeOrder` event in the `Magento\Authorizenet\Observer\AddFieldsToResponseObserver` file. -For information about an event in Magento 2, see [Events and observers](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). +For information about events in Magento 2, see [Events and observers](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). ### Layouts From 744cb5f5f71211bbf3dc69e32ada0a7057ce49f5 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Tue, 27 Aug 2019 01:56:24 +0100 Subject: [PATCH 428/841] Add changes requested during CR --- app/code/Magento/AsynchronousOperations/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/AsynchronousOperations/README.md b/app/code/Magento/AsynchronousOperations/README.md index f4678a0ebab22..896fddedab5fa 100644 --- a/app/code/Magento/AsynchronousOperations/README.md +++ b/app/code/Magento/AsynchronousOperations/README.md @@ -1,6 +1,6 @@ # Magento_AsynchronousOperations module -This component is designed to provide response for client who launched the bulk operation as soon as possible and postpone handling of operations moving them to background handler. +This component is designed to provide a response for a client that launched the bulk operation as soon as possible and postpone handling of operations moving them to the background handler. ## Installation details @@ -34,7 +34,7 @@ For more information about layouts in Magento 2, see the [Layout documentation]( ### UI components -You can extend Magento_AsynchronousOperations module using the following configuration files in `view/adminhtml/ui_component/` directory: +You can extend Magento_AsynchronousOperations module using the following configuration files in the `view/adminhtml/ui_component/` directory: - `bulk_details_form` - `bulk_details_form_modal` From cddb68dbe902e1a795cd450b372827282ae60ec0 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Tue, 27 Aug 2019 01:59:34 +0100 Subject: [PATCH 429/841] Add changes requested during CR --- app/code/Magento/AmqpStore/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/AmqpStore/README.md b/app/code/Magento/AmqpStore/README.md index 7ef84341f4419..88459a495401f 100644 --- a/app/code/Magento/AmqpStore/README.md +++ b/app/code/Magento/AmqpStore/README.md @@ -1,6 +1,6 @@ # Magento_AmqpStore module -Magento_AmqpStore provides ability to specify store before publish messages with with the Advanced Message Queuing Protocol (AMQP). +The Magento_AmqpStore module provides the ability to specify a store before publishing messages with the Advanced Message Queuing Protocol (AMQP). ## Extensibility From 6f51c11e095871bb6933a4e2ae2832faae37f250 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 26 Aug 2019 20:23:47 -0500 Subject: [PATCH 430/841] MC-19432: REST API returns 404 error instead of 400 --- .../Quote/Model/Quote/Item/Repository.php | 63 +++++++++---------- .../Quote/Api/CartItemRepositoryTest.php | 56 +++++++++++++++-- .../Webapi/ServiceInputProcessor.php | 13 ++++ 3 files changed, 96 insertions(+), 36 deletions(-) diff --git a/app/code/Magento/Quote/Model/Quote/Item/Repository.php b/app/code/Magento/Quote/Model/Quote/Item/Repository.php index 2b4852251bca2..1fb0a2d7107f1 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Repository.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Repository.php @@ -1,40 +1,34 @@ <?php /** + * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Quote\Model\Quote\Item; -use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotSaveException; -use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Quote\Api\CartItemRepositoryInterface; -use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Quote\Api\Data\CartItemInterfaceFactory; -/** - * Repository for quote item. - */ -class Repository implements CartItemRepositoryInterface +class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface { /** * Quote repository. * - * @var CartRepositoryInterface + * @var \Magento\Quote\Api\CartRepositoryInterface */ protected $quoteRepository; /** * Product repository. * - * @var ProductRepositoryInterface + * @var \Magento\Catalog\Api\ProductRepositoryInterface */ protected $productRepository; /** - * @var CartItemInterfaceFactory + * @var \Magento\Quote\Api\Data\CartItemInterfaceFactory */ protected $itemDataFactory; @@ -49,28 +43,25 @@ class Repository implements CartItemRepositoryInterface private $cartItemOptionsProcessor; /** - * @param CartRepositoryInterface $quoteRepository - * @param ProductRepositoryInterface $productRepository - * @param CartItemInterfaceFactory $itemDataFactory - * @param CartItemOptionsProcessor $cartItemOptionsProcessor + * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository + * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + * @param \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory * @param CartItemProcessorInterface[] $cartItemProcessors */ public function __construct( - CartRepositoryInterface $quoteRepository, - ProductRepositoryInterface $productRepository, - CartItemInterfaceFactory $itemDataFactory, - CartItemOptionsProcessor $cartItemOptionsProcessor, + \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, + \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, + \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory, array $cartItemProcessors = [] ) { $this->quoteRepository = $quoteRepository; $this->productRepository = $productRepository; $this->itemDataFactory = $itemDataFactory; - $this->cartItemOptionsProcessor = $cartItemOptionsProcessor; $this->cartItemProcessors = $cartItemProcessors; } /** - * @inheritdoc + * {@inheritdoc} */ public function getList($cartId) { @@ -80,26 +71,21 @@ public function getList($cartId) /** @var \Magento\Quote\Model\Quote\Item $item */ foreach ($quote->getAllVisibleItems() as $item) { - $item = $this->cartItemOptionsProcessor->addProductOptions($item->getProductType(), $item); - $output[] = $this->cartItemOptionsProcessor->applyCustomOptions($item); + $item = $this->getCartItemOptionsProcessor()->addProductOptions($item->getProductType(), $item); + $output[] = $this->getCartItemOptionsProcessor()->applyCustomOptions($item); } return $output; } /** - * @inheritdoc + * {@inheritdoc} */ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) { /** @var \Magento\Quote\Model\Quote $quote */ $cartId = $cartItem->getQuoteId(); - if (!$cartId) { - throw new InputException( - __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'cartId']) - ); - } - $quote = $this->quoteRepository->getActive($cartId); + $quoteItems = $quote->getItems(); $quoteItems[] = $cartItem; $quote->setItems($quoteItems); @@ -109,7 +95,7 @@ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) } /** - * @inheritdoc + * {@inheritdoc} */ public function deleteById($cartId, $itemId) { @@ -130,4 +116,17 @@ public function deleteById($cartId, $itemId) return true; } + + /** + * @return CartItemOptionsProcessor + * @deprecated 100.1.0 + */ + private function getCartItemOptionsProcessor() + { + if (!$this->cartItemOptionsProcessor instanceof CartItemOptionsProcessor) { + $this->cartItemOptionsProcessor = ObjectManager::getInstance()->get(CartItemOptionsProcessor::class); + } + + return $this->cartItemOptionsProcessor; + } } diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php index 15e10196f878d..a0dc9a8f80dc8 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php @@ -6,6 +6,7 @@ */ namespace Magento\Quote\Api; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\CustomOptions\CustomOptionProcessor; use Magento\Framework\Webapi\Rest\Request; use Magento\Quote\Model\Quote; @@ -83,9 +84,10 @@ public function testGetList() */ public function testAddItem() { - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class)->load(2); - $productSku = $product->getSku(); + $productSku = 'custom-design-simple-product'; + $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku); + /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $quote->load('test_order_1', 'reserved_order_id'); @@ -110,10 +112,56 @@ public function testAddItem() ], ]; $this->_webApiCall($serviceInfo, $requestData); - $this->assertTrue($quote->hasProductId(2)); + $this->assertTrue($quote->hasProductId($product->getId())); $this->assertEquals(7, $quote->getItemByProduct($product)->getQty()); } + /** + * @expectedException \Exception + * @expectedExceptionCode 400 + * @dataProvider failedAddItemDataProvider + * @param array|null $cartItem + */ + public function testFailedAddItem(?array $cartItem) + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'mine/items', + 'httpMethod' => Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + $requestData = [ + 'cartItem' => $cartItem, + ]; + $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * @return array + */ + public function failedAddItemDataProvider(): array + { + return [ + 'absent cart item' => [ + null, + ], + 'empty cart item' => [ + [], + ], + 'absent cart id' => [ + [ + 'sku' => 'custom-design-simple-product', + 'qty' => 7, + ], + ], + ]; + } + /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php */ diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index c253a400bed93..86d5e5a756bd9 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -243,6 +243,19 @@ protected function _createFromArray($className, $data) $className = substr($className, 0, -strlen('Interface')); } + $typeName = $this->typeProcessor->register($className); + $typeData = $this->typeProcessor->getTypeData($typeName); + $requiredFields = array_filter( + $typeData['parameters'], + function (array $fieldData) { + return $fieldData['required']; + } + ); + $missedRequiredFields = array_keys(array_diff_key($requiredFields, $data)); + if (!empty($missedRequiredFields)) { + $this->processInputError($missedRequiredFields); + } + // Primary method: assign to constructor parameters $constructorArgs = $this->getConstructorData($className, $data); $object = $this->objectManager->create($className, $constructorArgs); From 8048c09d7f37460c8dda1938fde85a2e36bce49b Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Mon, 26 Aug 2019 21:23:46 -0500 Subject: [PATCH 431/841] Adding verification of stock and catalog flag for showing out of stock products, the wishlist shows the products if they are out of stock with the flag show out of stock. --- app/code/Magento/Wishlist/Model/Wishlist.php | 160 ++++++++++++------- 1 file changed, 105 insertions(+), 55 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/Wishlist.php b/app/code/Magento/Wishlist/Model/Wishlist.php index 9797ab58b0766..bf43fbd6f4363 100644 --- a/app/code/Magento/Wishlist/Model/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Wishlist.php @@ -7,30 +7,49 @@ namespace Magento\Wishlist\Model; +use Exception; +use InvalidArgumentException; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ProductFactory; +use Magento\CatalogInventory\Model\Configuration; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DataObject; +use Magento\Framework\DataObject\IdentityInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Math\Random; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\Context; +use Magento\Framework\Registry; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\DateTime; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Wishlist\Helper\Data; use Magento\Wishlist\Model\ResourceModel\Item\CollectionFactory; use Magento\Wishlist\Model\ResourceModel\Wishlist as ResourceWishlist; use Magento\Wishlist\Model\ResourceModel\Wishlist\Collection; +use Magento\CatalogInventory\Model\Stock\StockItemRepository; /** * Wishlist model * * @method int getShared() - * @method \Magento\Wishlist\Model\Wishlist setShared(int $value) + * @method Wishlist setShared(int $value) * @method string getSharingCode() - * @method \Magento\Wishlist\Model\Wishlist setSharingCode(string $value) + * @method Wishlist setSharingCode(string $value) * @method string getUpdatedAt() - * @method \Magento\Wishlist\Model\Wishlist setUpdatedAt(string $value) + * @method Wishlist setUpdatedAt(string $value) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) * * @api * @since 100.0.2 */ -class Wishlist extends \Magento\Framework\Model\AbstractModel implements \Magento\Framework\DataObject\IdentityInterface +class Wishlist extends AbstractModel implements IdentityInterface { /** * Cache tag @@ -47,14 +66,14 @@ class Wishlist extends \Magento\Framework\Model\AbstractModel implements \Magent /** * Wishlist item collection * - * @var \Magento\Wishlist\Model\ResourceModel\Item\Collection + * @var ResourceModel\Item\Collection */ protected $_itemCollection; /** * Store filter for wishlist * - * @var \Magento\Store\Model\Store + * @var Store */ protected $_store; @@ -68,7 +87,7 @@ class Wishlist extends \Magento\Framework\Model\AbstractModel implements \Magent /** * Wishlist data * - * @var \Magento\Wishlist\Helper\Data + * @var Data */ protected $_wishlistData; @@ -80,12 +99,12 @@ class Wishlist extends \Magento\Framework\Model\AbstractModel implements \Magent protected $_catalogProduct; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $_storeManager; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime + * @var DateTime\DateTime */ protected $_date; @@ -100,17 +119,17 @@ class Wishlist extends \Magento\Framework\Model\AbstractModel implements \Magent protected $_wishlistCollectionFactory; /** - * @var \Magento\Catalog\Model\ProductFactory + * @var ProductFactory */ protected $_productFactory; /** - * @var \Magento\Framework\Math\Random + * @var Random */ protected $mathRandom; /** - * @var \Magento\Framework\Stdlib\DateTime + * @var DateTime */ protected $dateTime; @@ -129,43 +148,56 @@ class Wishlist extends \Magento\Framework\Model\AbstractModel implements \Magent */ private $serializer; + /** + * @var StockItemRepository + */ + private $stockItemRepository; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * Constructor * - * @param \Magento\Framework\Model\Context $context - * @param \Magento\Framework\Registry $registry + * @param Context $context + * @param Registry $registry * @param \Magento\Catalog\Helper\Product $catalogProduct - * @param \Magento\Wishlist\Helper\Data $wishlistData + * @param Data $wishlistData * @param ResourceWishlist $resource * @param Collection $resourceCollection - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Framework\Stdlib\DateTime\DateTime $date + * @param StoreManagerInterface $storeManager + * @param DateTime\DateTime $date * @param ItemFactory $wishlistItemFactory * @param CollectionFactory $wishlistCollectionFactory - * @param \Magento\Catalog\Model\ProductFactory $productFactory - * @param \Magento\Framework\Math\Random $mathRandom - * @param \Magento\Framework\Stdlib\DateTime $dateTime + * @param ProductFactory $productFactory + * @param Random $mathRandom + * @param DateTime $dateTime * @param ProductRepositoryInterface $productRepository + * @param StockItemRepository $stockItemRepository * @param bool $useCurrentWebsite * @param array $data * @param Json|null $serializer * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Framework\Model\Context $context, - \Magento\Framework\Registry $registry, + Context $context, + Registry $registry, \Magento\Catalog\Helper\Product $catalogProduct, - \Magento\Wishlist\Helper\Data $wishlistData, + Data $wishlistData, ResourceWishlist $resource, Collection $resourceCollection, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\Stdlib\DateTime\DateTime $date, + StoreManagerInterface $storeManager, + DateTime\DateTime $date, ItemFactory $wishlistItemFactory, CollectionFactory $wishlistCollectionFactory, - \Magento\Catalog\Model\ProductFactory $productFactory, - \Magento\Framework\Math\Random $mathRandom, - \Magento\Framework\Stdlib\DateTime $dateTime, + ProductFactory $productFactory, + Random $mathRandom, + DateTime $dateTime, ProductRepositoryInterface $productRepository, + StockItemRepository $stockItemRepository, + ScopeConfigInterface $scopeConfig = null, $useCurrentWebsite = true, array $data = [], Json $serializer = null @@ -183,6 +215,8 @@ public function __construct( $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->productRepository = $productRepository; + $this->stockItemRepository = $stockItemRepository; + $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -290,13 +324,13 @@ public function afterSave() /** * Add catalog product object data to wishlist * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @param int $qty * @param bool $forciblySetQty * * @return Item */ - protected function _addCatalogProduct(\Magento\Catalog\Model\Product $product, $qty = 1, $forciblySetQty = false) + protected function _addCatalogProduct(Product $product, $qty = 1, $forciblySetQty = false) { $item = null; foreach ($this->getItemCollection() as $_item) { @@ -311,7 +345,7 @@ protected function _addCatalogProduct(\Magento\Catalog\Model\Product $product, $ $item = $this->_wishlistItemFactory->create(); $item->setProductId($product->getId()); $item->setWishlistId($this->getId()); - $item->setAddedAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT)); + $item->setAddedAt((new \DateTime())->format(DateTime::DATETIME_PHP_FORMAT)); $item->setStoreId($storeId); $item->setOptions($product->getCustomOptions()); $item->setProduct($product); @@ -333,7 +367,7 @@ protected function _addCatalogProduct(\Magento\Catalog\Model\Product $product, $ /** * Retrieve wishlist item collection * - * @return \Magento\Wishlist\Model\ResourceModel\Item\Collection + * @return ResourceModel\Item\Collection */ public function getItemCollection() { @@ -365,8 +399,9 @@ public function getItem($itemId) /** * Adding item to wishlist * - * @param Item $item + * @param Item $item * @return $this + * @throws Exception */ public function addItem(Item $item) { @@ -383,13 +418,13 @@ public function addItem(Item $item) * * Returns new item or string on error. * - * @param int|\Magento\Catalog\Model\Product $product - * @param \Magento\Framework\DataObject|array|string|null $buyRequest + * @param int|Product $product + * @param DataObject|array|string|null $buyRequest * @param bool $forciblySetQty - * @throws \Magento\Framework\Exception\LocalizedException * @return Item|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + *@throws LocalizedException */ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false) { @@ -398,7 +433,7 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false * a) we have new instance and do not interfere with other products in wishlist * b) product has full set of attributes */ - if ($product instanceof \Magento\Catalog\Model\Product) { + if ($product instanceof Product) { $productId = $product->getId(); // Maybe force some store by wishlist internal properties $storeId = $product->hasWishlistStoreId() ? $product->getWishlistStoreId() : $product->getStoreId(); @@ -414,10 +449,19 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false try { $product = $this->productRepository->getById($productId, false, $storeId); } catch (NoSuchEntityException $e) { - throw new \Magento\Framework\Exception\LocalizedException(__('Cannot specify product.')); + throw new LocalizedException(__('Cannot specify product.')); + } + + $stockItem = $this->stockItemRepository->get($product->getId()); + $showOutOfStock = $this->scopeConfig->isSetFlag( + Configuration::XML_PATH_SHOW_OUT_OF_STOCK, + ScopeInterface::SCOPE_STORE + ); + if (!$stockItem->getIsInStock() && !$showOutOfStock) { + throw new LocalizedException(__('Cannot add product without stock to wishlist.')); } - if ($buyRequest instanceof \Magento\Framework\DataObject) { + if ($buyRequest instanceof DataObject) { $_buyRequest = $buyRequest; } elseif (is_string($buyRequest)) { $isInvalidItemConfiguration = false; @@ -426,20 +470,20 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false if (!is_array($buyRequestData)) { $isInvalidItemConfiguration = true; } - } catch (\InvalidArgumentException $exception) { + } catch (InvalidArgumentException $exception) { $isInvalidItemConfiguration = true; } if ($isInvalidItemConfiguration) { - throw new \InvalidArgumentException('Invalid wishlist item configuration.'); + throw new InvalidArgumentException('Invalid wishlist item configuration.'); } - $_buyRequest = new \Magento\Framework\DataObject($buyRequestData); + $_buyRequest = new DataObject($buyRequestData); } elseif (is_array($buyRequest)) { - $_buyRequest = new \Magento\Framework\DataObject($buyRequest); + $_buyRequest = new DataObject($buyRequest); } else { - $_buyRequest = new \Magento\Framework\DataObject(); + $_buyRequest = new DataObject(); } - /* @var $product \Magento\Catalog\Model\Product */ + /* @var $product Product */ $cartCandidates = $product->getTypeInstance()->processConfiguration($_buyRequest, clone $product); /** @@ -486,6 +530,7 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false * * @param int $customerId * @return $this + * @throws LocalizedException */ public function setCustomerId($customerId) { @@ -496,6 +541,7 @@ public function setCustomerId($customerId) * Retrieve customer id * * @return int + * @throws LocalizedException */ public function getCustomerId() { @@ -506,6 +552,7 @@ public function getCustomerId() * Retrieve data for save * * @return array + * @throws LocalizedException */ public function getDataForSave() { @@ -520,6 +567,7 @@ public function getDataForSave() * Retrieve shared store ids for current website or all stores if $current is false * * @return array + * @throws NoSuchEntityException */ public function getSharedStoreIds() { @@ -553,7 +601,8 @@ public function setSharedStoreIds($storeIds) /** * Retrieve wishlist store object * - * @return \Magento\Store\Model\Store + * @return Store + * @throws NoSuchEntityException */ public function getStore() { @@ -566,7 +615,7 @@ public function getStore() /** * Set wishlist store * - * @param \Magento\Store\Model\Store $store + * @param Store $store * @return $this */ public function setStore($store) @@ -605,6 +654,7 @@ public function isSalable() * * @param int $customerId * @return bool + * @throws LocalizedException */ public function isOwner($customerId) { @@ -626,10 +676,10 @@ public function isOwner($customerId) * For more options see \Magento\Catalog\Helper\Product->addParamsToBuyRequest() * * @param int|Item $itemId - * @param \Magento\Framework\DataObject $buyRequest - * @param null|array|\Magento\Framework\DataObject $params + * @param DataObject $buyRequest + * @param null|array|DataObject $params * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * * @see \Magento\Catalog\Helper\Product::addParamsToBuyRequest() * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -645,16 +695,16 @@ public function updateItem($itemId, $buyRequest, $params = null) $item = $this->getItem((int)$itemId); } if (!$item) { - throw new \Magento\Framework\Exception\LocalizedException(__('We can\'t specify a wish list item.')); + throw new LocalizedException(__('We can\'t specify a wish list item.')); } $product = $item->getProduct(); $productId = $product->getId(); if ($productId) { if (!$params) { - $params = new \Magento\Framework\DataObject(); + $params = new DataObject(); } elseif (is_array($params)) { - $params = new \Magento\Framework\DataObject($params); + $params = new DataObject($params); } $params->setCurrentConfig($item->getBuyRequest()); $buyRequest = $this->_catalogProduct->addParamsToBuyRequest($buyRequest, $params); @@ -677,7 +727,7 @@ public function updateItem($itemId, $buyRequest, $params = null) * Error message */ if (is_string($resultItem)) { - throw new \Magento\Framework\Exception\LocalizedException(__($resultItem)); + throw new LocalizedException(__($resultItem)); } if ($resultItem->getId() != $itemId) { @@ -691,7 +741,7 @@ public function updateItem($itemId, $buyRequest, $params = null) $resultItem->setOrigData('qty', 0); } } else { - throw new \Magento\Framework\Exception\LocalizedException(__('The product does not exist.')); + throw new LocalizedException(__('The product does not exist.')); } return $this; } From 52ff0be4aa692cabea66932bc5e60228a7abeaaf Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Mon, 26 Aug 2019 22:17:07 -0500 Subject: [PATCH 432/841] MC-19432: REST API returns 404 error instead of 400 --- .../Webapi/ServiceInputProcessor.php | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 86d5e5a756bd9..f9995cbc6bf00 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -245,13 +245,21 @@ protected function _createFromArray($className, $data) $typeName = $this->typeProcessor->register($className); $typeData = $this->typeProcessor->getTypeData($typeName); - $requiredFields = array_filter( - $typeData['parameters'], - function (array $fieldData) { - return $fieldData['required']; - } + $requiredFields = array_keys( + array_filter( + $typeData['parameters'], + function (array $fieldData) { + return $fieldData['required']; + } + ) + ); + $camelCaseData = array_map( + function (string $value) { + return SimpleDataObjectConverter::snakeCaseToCamelCase($value); + }, + array_keys($data) ); - $missedRequiredFields = array_keys(array_diff_key($requiredFields, $data)); + $missedRequiredFields = array_diff($requiredFields, $camelCaseData); if (!empty($missedRequiredFields)) { $this->processInputError($missedRequiredFields); } From afc57f92bb4a2deb13dcf88907eb6686957d9e9e Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 27 Aug 2019 12:22:04 +0700 Subject: [PATCH 433/841] Resolve No validate "Day" in PayPal Express Checkout Settings issue24302 --- .../Magento/Paypal/etc/adminhtml/system/express_checkout.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index d5287659ee7d2..86887ae2b6cb7 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -348,6 +348,7 @@ <depends> <field id="payment_action">Order</field> </depends> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="order_valid_period" translate="label comment" type="text" sortOrder="60" showInDefault="1" showInWebsite="1"> <label>Order Valid Period (days)</label> @@ -357,6 +358,7 @@ <depends> <field id="payment_action">Order</field> </depends> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="child_authorization_number" translate="label comment" type="text" sortOrder="70" showInDefault="1" showInWebsite="1"> <label>Number of Child Authorizations</label> From 35befe1985f88d8832a1b3654329a288b29bc4ab Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 27 Aug 2019 12:33:57 +0700 Subject: [PATCH 434/841] Resolve No validate "Proxy Port" in Paypal Module (Paypal Express, Paypal Payflow -Link, Pro, Advanced) issue24304 --- .../Magento/Paypal/etc/adminhtml/system/express_checkout.xml | 1 + .../Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml | 1 + app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml | 1 + .../Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml | 1 + 4 files changed, 4 insertions(+) diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index d5287659ee7d2..9dffb61b6f960 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -124,6 +124,7 @@ <depends> <field id="use_proxy">1</field> </depends> + <validate>validate-digits validate-zero-or-greater</validate> </field> </group> <field id="enable_express_checkout" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml index cba36916c3305..4d9b7dcf0492d 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml @@ -76,6 +76,7 @@ <field id="use_proxy">1</field> </depends> <attribute type="shared">1</attribute> + <validate>validate-digits validate-zero-or-greater</validate> </field> <field id="payflow_advanced_info" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Payflowlink\Advanced</frontend_model> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml index ed11e8ba18d07..234ed5c37c1b6 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml @@ -77,6 +77,7 @@ <field id="use_proxy">1</field> </depends> <attribute type="shared">1</attribute> + <validate>validate-digits validate-zero-or-greater</validate> </field> <field id="payflowlink_info" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Payflowlink\Info</frontend_model> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml index e5a0319bdc1bf..7930027d3d085 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml @@ -72,6 +72,7 @@ <field id="use_proxy">1</field> </depends> <attribute type="shared">1</attribute> + <validate>validate-digits validate-zero-or-greater</validate> </field> </group> <field id="enable_paypal_payflow" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> From b6f2b5cb0a35ade677fb281b488882edd4fc783e Mon Sep 17 00:00:00 2001 From: rani-webkul <rani.priya4392webkul.com> Date: Tue, 27 Aug 2019 11:08:48 +0530 Subject: [PATCH 435/841] Resolved edit configurable product from cart doesn't show selected swatch options #24306 --- .../Magento/Swatches/view/frontend/web/js/swatch-renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index d6302cff83bff..dede596adfec1 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -750,7 +750,7 @@ define([ $(document).trigger('updateMsrpPriceBlock', [ - parseInt($this.attr('index'), 10) + 1, + _.findKey($widget.options.jsonConfig.index, $widget.options.jsonConfig.defaultValues), $widget.options.jsonConfig.optionPrices ]); From 57db0ea78210d5d70ff844aad7ad5ee29ef67d9b Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 27 Aug 2019 12:44:27 +0700 Subject: [PATCH 436/841] Resolve Many field has no validate number in Checkout settings issue24307 --- app/code/Magento/Checkout/etc/adminhtml/system.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/Checkout/etc/adminhtml/system.xml b/app/code/Magento/Checkout/etc/adminhtml/system.xml index 11e3ba5f3ed9a..c66ce0b487790 100644 --- a/app/code/Magento/Checkout/etc/adminhtml/system.xml +++ b/app/code/Magento/Checkout/etc/adminhtml/system.xml @@ -27,12 +27,14 @@ </field> <field id="max_items_display_count" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum Number of Items to Display in Order Summary</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> </group> <group id="cart" translate="label" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Shopping Cart</label> <field id="delete_quote_after" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Quote Lifetime (days)</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="redirect_to_cart" translate="label" type="select" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>After Adding a Product Redirect to Shopping Cart</label> @@ -40,6 +42,7 @@ </field> <field id="number_items_to_display_pager" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Number of Items to Display Pager</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="crosssell_enabled" translate="label" type="select" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Show Cross-sell Items in the Shopping Cart</label> @@ -61,9 +64,11 @@ </field> <field id="count" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Number of Items to Display Scrollbar</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="max_items_display_count" translate="label" type="text" sortOrder="3" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum Number of Items to Display</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> </group> <group id="payment_failed" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="1"> From 66f8376d8d40d43c54c2ae6becd721eb16b4f9de Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 27 Aug 2019 13:05:44 +0700 Subject: [PATCH 437/841] Resolve "Send Payment Failed Email Copy To" field has no validate email issue24312 --- app/code/Magento/Checkout/etc/adminhtml/system.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Checkout/etc/adminhtml/system.xml b/app/code/Magento/Checkout/etc/adminhtml/system.xml index 11e3ba5f3ed9a..64dae36c4b719 100644 --- a/app/code/Magento/Checkout/etc/adminhtml/system.xml +++ b/app/code/Magento/Checkout/etc/adminhtml/system.xml @@ -83,6 +83,7 @@ </field> <field id="copy_to" translate="label comment" type="text" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Send Payment Failed Email Copy To</label> + <validate>validate-emails</validate> <comment>Separate by ",".</comment> </field> <field id="copy_method" translate="label" type="select" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1"> From 8cc0b25f98aad08fc00c40dc63dd1d983e068d7e Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 27 Aug 2019 14:49:33 +0700 Subject: [PATCH 438/841] Validate port range issue 24304 --- .../Magento/Paypal/etc/adminhtml/system/express_checkout.xml | 2 +- .../Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml | 2 +- app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml | 2 +- .../Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 9dffb61b6f960..1d72d3a86e4be 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -124,7 +124,7 @@ <depends> <field id="use_proxy">1</field> </depends> - <validate>validate-digits validate-zero-or-greater</validate> + <validate>validate-digits validate-digits-range digits-range-0-65535</validate> </field> </group> <field id="enable_express_checkout" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml index 4d9b7dcf0492d..492d4b2ac9ce4 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml @@ -76,7 +76,7 @@ <field id="use_proxy">1</field> </depends> <attribute type="shared">1</attribute> - <validate>validate-digits validate-zero-or-greater</validate> + <validate>validate-digits validate-digits-range digits-range-0-65535</validate> </field> <field id="payflow_advanced_info" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Payflowlink\Advanced</frontend_model> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml index 234ed5c37c1b6..2e25dd6a66124 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml @@ -77,7 +77,7 @@ <field id="use_proxy">1</field> </depends> <attribute type="shared">1</attribute> - <validate>validate-digits validate-zero-or-greater</validate> + <validate>validate-digits validate-digits-range digits-range-0-65535</validate> </field> <field id="payflowlink_info" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Payflowlink\Info</frontend_model> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml index 7930027d3d085..91396d2a95ac2 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml @@ -72,7 +72,7 @@ <field id="use_proxy">1</field> </depends> <attribute type="shared">1</attribute> - <validate>validate-digits validate-zero-or-greater</validate> + <validate>validate-digits validate-digits-range digits-range-0-65535</validate> </field> </group> <field id="enable_paypal_payflow" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1"> From 35620e2723c70ee1284682fe4abd2504594e2e6b Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Tue, 27 Aug 2019 11:04:42 +0300 Subject: [PATCH 439/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix strict types. --- app/code/Magento/Catalog/Model/ResourceModel/Category.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index d50570930923e..b6f565f5fae4e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -355,7 +355,7 @@ protected function _getMaxPosition($path) { $connection = $this->getConnection(); $positionField = $connection->quoteIdentifier('position'); - $level = count(explode('/', $path)); + $level = count(explode('/', (string)$path)); $bind = ['c_level' => $level, 'c_path' => $path . '/%']; $select = $connection->select()->from( $this->getTable('catalog_category_entity'), @@ -720,7 +720,7 @@ public function getCategories($parent, $recursionLevel = 0, $sorted = false, $as */ public function getParentCategories($category) { - $pathIds = array_reverse(explode(',', $category->getPathInStore())); + $pathIds = array_reverse(explode(',', (string)$category->getPathInStore())); /** @var \Magento\Catalog\Model\ResourceModel\Category\Collection $categories */ $categories = $this->_categoryCollectionFactory->create(); return $categories->setStore( From 5f014be7b3f385faddc99c80e0e88b673aa679ab Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Tue, 27 Aug 2019 11:33:53 +0300 Subject: [PATCH 440/841] magento/magento2#23818: Static test fix. --- lib/internal/Magento/Framework/Model/EntitySnapshot.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot.php b/lib/internal/Magento/Framework/Model/EntitySnapshot.php index 8062bf352ced1..6ae7cb346a7df 100644 --- a/lib/internal/Magento/Framework/Model/EntitySnapshot.php +++ b/lib/internal/Magento/Framework/Model/EntitySnapshot.php @@ -45,6 +45,8 @@ public function __construct( } /** + * Register snapshot of entity data. + * * @param string $entityType * @param object $entity * @return void From bac43b476c67c5497ae14cda336ea8c2ed66e2a5 Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Tue, 27 Aug 2019 11:58:06 +0300 Subject: [PATCH 441/841] MC-15140: Paypal Express Checkout - When user hits Cancel button on pop-up "There is already another PayPal solution enabled...." then "Enable this Solution" drop down still says "Yes" instead of "No" --- ...urchaseProductWithCustomOptionsWithLongValuesTitle.xml | 3 +++ .../Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml index 1bb3bcc69cb57..e9e23cf157a26 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml @@ -17,6 +17,9 @@ <description value="Admin should be able to see the full title of the selected custom option value in the order"/> <severity value="MAJOR"/> <testCaseId value="MC-3043"/> + <skip> + <issueId value="MQE-1128"/> + </skip> </annotations> <before> <!--Create Simple Product with Custom Options--> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml index 25e93f8f6ff4c..ed879a82d3f59 100644 --- a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml @@ -21,6 +21,14 @@ <createData stepKey="b2" entity="customStoreGroup"/> </before> <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCustomStoreGroup"> + <argument name="storeGroupName" value="customStoreGroup.name" /> + </actionGroup> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCustomStoreGroup2"> + <argument name="storeGroupName" value="customStoreGroup.name" /> + </actionGroup> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> </after> From 578c043502d4e591b98bd7417537d5bcc94b0add Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Tue, 27 Aug 2019 12:30:46 +0300 Subject: [PATCH 442/841] magento/magento2#18678: Static tests fix. --- app/code/Magento/Checkout/Model/Session.php | 1 + .../Magento/TestModuleQuoteTotalsObserver/Model/Config.php | 1 - .../Observer/AfterCollectTotals.php | 7 ++++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Checkout/Model/Session.php b/app/code/Magento/Checkout/Model/Session.php index 1fd25487ec56c..a654c78853d7a 100644 --- a/app/code/Magento/Checkout/Model/Session.php +++ b/app/code/Magento/Checkout/Model/Session.php @@ -17,6 +17,7 @@ * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + * @SuppressWarnings(PHPMD.TooManyFields) */ class Session extends \Magento\Framework\Session\SessionManager { diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php index 74d9d51278814..7eca15cc5d94a 100644 --- a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php @@ -26,4 +26,3 @@ public function isActive() return $this->active; } } - diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php index 02e3b42686d22..7cc6504dbfb75 100644 --- a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php +++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php @@ -27,8 +27,10 @@ class AfterCollectTotals implements ObserverInterface * @param \Magento\Checkout\Model\Session $messageManager * @param \Magento\TestModuleQuoteTotalsObserver\Model\Config $config */ - public function __construct(\Magento\Checkout\Model\Session $messageManager, \Magento\TestModuleQuoteTotalsObserver\Model\Config $config) - { + public function __construct( + \Magento\Checkout\Model\Session $messageManager, + \Magento\TestModuleQuoteTotalsObserver\Model\Config $config + ) { $this->config = $config; $this->session = $messageManager; } @@ -45,4 +47,3 @@ public function execute(\Magento\Framework\Event\Observer $observer) } } } - From 140e43f7c1c149a62d8bfeb91aeac72bba882834 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Tue, 27 Aug 2019 13:58:28 +0300 Subject: [PATCH 443/841] magento/magento2#22810: Unit and static tests fix. --- app/code/Magento/Sales/Model/Order/Email/Sender.php | 3 ++- .../Order/Email/Sender/CreditmemoSenderTest.php | 2 +- .../Model/Order/Email/Sender/InvoiceSenderTest.php | 2 +- .../Model/Order/Email/Sender/OrderSenderTest.php | 4 ++-- .../Model/Order/Email/Sender/ShipmentSenderTest.php | 2 +- .../Unit/Model/Order/Email/SenderBuilderTest.php | 13 ++++++++----- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender.php b/app/code/Magento/Sales/Model/Order/Email/Sender.php index 529da68db5346..ab1a158336d30 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender.php @@ -12,8 +12,9 @@ /** * Class Sender - * @api * + * phpcs:disable Magento2.Classes.AbstractApi + * @api * @since 100.0.2 */ abstract class Sender diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php index 8047a5e71361f..287daa2fba4b9 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php @@ -57,7 +57,7 @@ protected function setUp() $this->identityContainerMock = $this->createPartialMock( \Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity::class, - ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId'] + ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod'] ); $this->identityContainerMock->expects($this->any()) ->method('getStore') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php index 212fccc55538b..3315ec8eb4196 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php @@ -57,7 +57,7 @@ protected function setUp() $this->identityContainerMock = $this->createPartialMock( \Magento\Sales\Model\Order\Email\Container\InvoiceIdentity::class, - ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId'] + ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod'] ); $this->identityContainerMock->expects($this->any()) ->method('getStore') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php index 8adebfc99a47f..bfea2d63ef1bb 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php @@ -30,7 +30,7 @@ protected function setUp() $this->identityContainerMock = $this->createPartialMock( \Magento\Sales\Model\Order\Email\Container\OrderIdentity::class, - ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId'] + ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod'] ); $this->identityContainerMock->expects($this->any()) ->method('getStore') @@ -77,7 +77,7 @@ public function testSend($configValue, $forceSyncMode, $emailSendingResult, $sen ->willReturn($emailSendingResult); if ($emailSendingResult) { - $this->identityContainerMock->expects($this->once()) + $this->identityContainerMock->expects($senderSendException ? $this->never() : $this->once()) ->method('getCopyMethod') ->willReturn('copy'); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php index fc7796f63cdab..96bbb1aea7abd 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php @@ -57,7 +57,7 @@ protected function setUp() $this->identityContainerMock = $this->createPartialMock( \Magento\Sales\Model\Order\Email\Container\ShipmentIdentity::class, - ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId'] + ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod'] ); $this->identityContainerMock->expects($this->any()) ->method('getStore') diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php index 650955eca5c70..adfb697e70331 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php @@ -48,11 +48,14 @@ protected function setUp() ['getTemplateVars', 'getTemplateOptions', 'getTemplateId'] ); - $this->storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, [ - 'getStoreId', - '__wakeup', - 'getId', - ]); + $this->storeMock = $this->createPartialMock( + \Magento\Store\Model\Store::class, + [ + 'getStoreId', + '__wakeup', + 'getId', + ] + ); $this->identityContainerMock = $this->createPartialMock( \Magento\Sales\Model\Order\Email\Container\ShipmentIdentity::class, From 5f7029d71e06e96d8e5c8a6197ef9343c0151c17 Mon Sep 17 00:00:00 2001 From: konarshankar07 <konar.shankar2013@gmail.com> Date: Tue, 27 Aug 2019 18:53:20 +0530 Subject: [PATCH 444/841] Fixed issue with Review form reset button --- app/code/Magento/Review/Block/Adminhtml/Add.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/code/Magento/Review/Block/Adminhtml/Add.php b/app/code/Magento/Review/Block/Adminhtml/Add.php index 260685395e106..27d1e28c2e478 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Add.php +++ b/app/code/Magento/Review/Block/Adminhtml/Add.php @@ -30,6 +30,7 @@ protected function _construct() $this->buttonList->update('save', 'id', 'save_button'); $this->buttonList->update('reset', 'id', 'reset_button'); + $this->buttonList->update('reset', 'onclick', 'window.review.formReset()'); $this->_formScripts[] = ' require(["prototype"], function(){ @@ -44,6 +45,7 @@ protected function _construct() require(["jquery","prototype"], function(jQuery){ window.review = function() { return { + reviewFormEditSelector: "#edit_form", productInfoUrl : null, formHidden : true, gridRowClick : function(data, click) { @@ -72,6 +74,9 @@ protected function _construct() toggleVis("save_button"); toggleVis("reset_button"); }, + formReset: function() { + jQuery(review.reviewFormEditSelector).trigger(\'reset\'); + }, updateRating: function() { elements = [$("select_stores"), $("rating_detail").getElementsBySelector("input[type=\'radio\']")].flatten(); $(\'save_button\').disabled = true; From 71daa8e854ba6ebb36a275f8474520896946ae36 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Tue, 27 Aug 2019 17:04:32 +0300 Subject: [PATCH 445/841] MC-19438: Bundle Product Cart Pricing issue when adding tier price to bundle product and item is added twice to the cart --- .../Plugin/UpdatePriceInQuoteItemOptions.php | 55 ------------------- app/code/Magento/Bundle/etc/di.xml | 3 - 2 files changed, 58 deletions(-) delete mode 100644 app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php diff --git a/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php b/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php deleted file mode 100644 index acff09c60522a..0000000000000 --- a/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Bundle\Plugin; - -use Magento\Quote\Model\Quote\Item as OrigQuoteItem; -use Magento\Quote\Model\Quote\Item\AbstractItem; -use Magento\Framework\Serialize\SerializerInterface; - -/** - * Update prices stored in quote item options after calculating quote item's totals - */ -class UpdatePriceInQuoteItemOptions -{ - /** - * @var SerializerInterface - */ - private $serializer; - - /** - * @param SerializerInterface $serializer - */ - public function __construct(SerializerInterface $serializer) - { - $this->serializer = $serializer; - } - - /** - * Update price on quote item options level - * - * @param OrigQuoteItem $subject - * @param AbstractItem $result - * @return AbstractItem - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterCalcRowTotal(OrigQuoteItem $subject, AbstractItem $result) - { - $bundleAttributes = $result->getProduct()->getCustomOption('bundle_selection_attributes'); - if ($bundleAttributes !== null) { - $actualPrice = (float)$result->getPrice(); - $parsedValue = $this->serializer->unserialize($bundleAttributes->getValue()); - if (is_array($parsedValue) && array_key_exists('price', $parsedValue)) { - $parsedValue['price'] = $actualPrice; - } - $bundleAttributes->setValue($this->serializer->serialize($parsedValue)); - } - - return $result; - } -} diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml index 72155d922a25f..d0e956efee694 100644 --- a/app/code/Magento/Bundle/etc/di.xml +++ b/app/code/Magento/Bundle/etc/di.xml @@ -123,9 +123,6 @@ </argument> </arguments> </type> - <type name="Magento\Quote\Model\Quote\Item"> - <plugin name="update_price_for_bundle_in_quote_item_option" type="Magento\Bundle\Plugin\UpdatePriceInQuoteItemOptions"/> - </type> <type name="Magento\Quote\Model\Quote\Item\ToOrderItem"> <plugin name="append_bundle_data_to_order" type="Magento\Bundle\Model\Plugin\QuoteItem"/> </type> From c10285952ecb3e1da14f77dff1842ebf66a90339 Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Fri, 23 Aug 2019 17:55:04 -0500 Subject: [PATCH 446/841] MC-19306: Store creation ('app:config:import' with pre-defined stores) fails during installation - Fix lock wait timeout exceeded --- app/code/Magento/Store/Model/Store.php | 29 +++++------ .../AdminCreateNewStoreGroupActionGroup.xml | 44 +++++++++++++++++ .../Section/AdminEditStoreGroupSection.xml | 12 +++++ .../Test/AdminDeleteDefaultStoreViewTest.xml | 48 +++++++++++++++++++ 4 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 app/code/Magento/Store/Test/Mftf/Section/AdminEditStoreGroupSection.xml create mode 100644 app/code/Magento/Store/Test/Mftf/Test/AdminDeleteDefaultStoreViewTest.xml diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index dab9c55c216d9..0bc371da0aab9 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -912,7 +912,7 @@ public function setCurrentCurrencyCode($code) $defaultCode = ($this->_storeManager->getStore() !== null) ? $this->_storeManager->getStore()->getDefaultCurrency()->getCode() : $this->_storeManager->getWebsite()->getDefaultStore()->getDefaultCurrency()->getCode(); - + $this->_httpContext->setValue(Context::CONTEXT_CURRENCY, $code, $defaultCode); } return $this; @@ -1279,7 +1279,20 @@ public function isActive() public function beforeDelete() { $this->_configDataResource->clearScopeData(ScopeInterface::SCOPE_STORES, $this->getId()); - return parent::beforeDelete(); + parent::beforeDelete(); + if ($this->getId() === $this->getGroup()->getDefaultStoreId()) { + $ids = $this->getGroup()->getStoreIds(); + if (!empty($ids) && count($ids) > 1) { + unset($ids[$this->getId()]); + $defaultId = current($ids); + } else { + $defaultId = null; + } + $this->getGroup()->setDefaultStoreId($defaultId); + $this->getGroup()->save(); + } + + return $this; } /** @@ -1300,18 +1313,6 @@ function () use ($store) { parent::afterDelete(); $this->_configCacheType->clean(); - if ($this->getId() === $this->getGroup()->getDefaultStoreId()) { - $ids = $this->getGroup()->getStoreIds(); - if (!empty($ids) && count($ids) > 1) { - unset($ids[$this->getId()]); - $defaultId = current($ids); - } else { - $defaultId = null; - } - $this->getGroup()->setDefaultStoreId($defaultId); - $this->getGroup()->save(); - } - return $this; } diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml index cd9016ebf6d7f..4c00071da6b61 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml @@ -71,6 +71,50 @@ <see selector="{{AdminStoresGridSection.nthRow('1')}}" userInput="{{storeGroupName}}" stepKey="seeAssertStoreGroupInGridMessage"/> </actionGroup> + <actionGroup name="EditStoreGroupActionGroup"> + <annotations> + <description>Edit store group.</description> + </annotations> + <arguments> + <argument name="storeGroupName" type="string"/> + </arguments> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForAdminSystemStorePageLoad"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> + <waitForPageLoad stepKey="waitForResetResult"/> + <fillField userInput="{{storeGroupName}}" selector="{{AdminStoresGridSection.storeGrpFilterTextField}}" stepKey="fillStoreGroupFilter"/> + <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResult"/> + <click selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="clicksStoreGroupName"/> + <waitForPageLoad stepKey="waitForStorePageToLoad"/> + </actionGroup> + + <actionGroup name="ChangeDefaultStoreViewActionGroup" extends="EditStoreGroupActionGroup"> + <annotations> + <description>Change the default store view for provided store group.</description> + </annotations> + <arguments> + <argument name="storeViewName" type="string"/> + </arguments> + <selectOption selector="{{AdminEditStoreGroupSection.defaultStoreView}}" userInput="{{storeViewName}}" stepKey="changeDefaultStoreView" after="waitForStorePageToLoad"/> + <click selector="{{AdminStoreGroupActionsSection.saveButton}}" stepKey="clickSaveStoreGroup"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForModal"/> + <see selector="{{AdminConfirmationModalSection.title}}" userInput="Warning message" stepKey="seeWarningAboutTakingALongTimeToComplete"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmModal"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="seeForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the store." stepKey="seeSuccessMessage"/> + </actionGroup> + + <actionGroup name="AssertDefaultStoreViewActionGroup" extends="EditStoreGroupActionGroup"> + <annotations> + <description>Asserts that the provided store view is default in provided store group.</description> + </annotations> + <arguments> + <argument name="storeViewName" type="string"/> + </arguments> + <seeOptionIsSelected selector="{{AdminEditStoreGroupSection.defaultStoreView}}" userInput="{{storeViewName}}" stepKey="assertDefaultStoreView" after="waitForStorePageToLoad"/> + </actionGroup> + <actionGroup name="AssertStoreGroupForm"> <annotations> <description>Clicks on the 1st Store in the 'Stores' grid. Validates that the provided Details (Website, Store Group Name, Store Group Code and Root Category) are present and correct.</description> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminEditStoreGroupSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminEditStoreGroupSection.xml new file mode 100644 index 0000000000000..343373c61da9b --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminEditStoreGroupSection.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminEditStoreGroupSection"> + <element name="defaultStoreView" type="select" selector="#group_default_store_id"/> + </section> +</sections> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteDefaultStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteDefaultStoreViewTest.xml new file mode 100644 index 0000000000000..85fd8561f90b5 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteDefaultStoreViewTest.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!-- Test XML Example --> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteDefaultStoreViewTest"> + <annotations> + <stories value="Store creation ('app:config:import' with pre-defined stores) fails during installation"/> + <title value="Delete Default Store View"/> + <description value="Test another store view should be set as default when default store view is deleted"/> + <testCaseId value="MC-19306"/> + <severity value="CRITICAL"/> + <group value="store"/> + </annotations> + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Create custom store view--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createNewStoreView"> + <argument name="StoreGroup" value="_defaultStoreGroup"/> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + + <!--Change the default store view to the custom store view--> + <actionGroup ref="ChangeDefaultStoreViewActionGroup" stepKey="changeDefaultStoreViewToCustomStoreView"> + <argument name="storeGroupName" value="{{_defaultStoreGroup.name}}"/> + <argument name="storeViewName" value="{{storeViewData.name}}"/> + </actionGroup> + + <!--Delete custom store view --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteCustomStoreView"> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + + <!--Verify that the default store view is now the default store view--> + <actionGroup ref="AssertDefaultStoreViewActionGroup" stepKey="assertDefaultStoreViewActionGroup"> + <argument name="storeGroupName" value="{{_defaultStoreGroup.name}}"/> + <argument name="storeViewName" value="{{_defaultStore.name}}"/> + </actionGroup> + </test> +</tests> From 211f2a3860562c73a9f65fbaeea2761411bda213 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 27 Aug 2019 09:28:07 -0500 Subject: [PATCH 447/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Add relevance to sort options - Refactor and decouple searching and filtering --- .../Products/DataProvider/Product.php | 30 ++-- .../Product/CollectionPostProcessor.php | 42 +++++ .../Products/DataProvider/ProductSearch.php | 144 ++++++++++++++++++ .../Products/Query/FieldSelection.php | 93 +++++++++++ .../Model/Resolver/Products/Query/Filter.php | 69 +-------- .../Model/Resolver/Products/Query/Search.php | 88 +++++------ .../CatalogGraphQl/etc/schema.graphqls | 1 + 7 files changed, 339 insertions(+), 128 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionPostProcessor.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php index e5e0d1aea4285..2076ec6726988 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php @@ -8,6 +8,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider; use Magento\Catalog\Model\Product\Visibility; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessor; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory; @@ -32,7 +33,12 @@ class Product /** * @var CollectionProcessorInterface */ - private $collectionProcessor; + private $collectionPreProcessor; + + /** + * @var CollectionPostProcessor + */ + private $collectionPostProcessor; /** * @var Visibility @@ -44,17 +50,20 @@ class Product * @param ProductSearchResultsInterfaceFactory $searchResultsFactory * @param Visibility $visibility * @param CollectionProcessorInterface $collectionProcessor + * @param CollectionPostProcessor $collectionPostProcessor */ public function __construct( CollectionFactory $collectionFactory, ProductSearchResultsInterfaceFactory $searchResultsFactory, Visibility $visibility, - CollectionProcessorInterface $collectionProcessor + CollectionProcessorInterface $collectionProcessor, + CollectionPostProcessor $collectionPostProcessor ) { $this->collectionFactory = $collectionFactory; $this->searchResultsFactory = $searchResultsFactory; $this->visibility = $visibility; - $this->collectionProcessor = $collectionProcessor; + $this->collectionPreProcessor = $collectionProcessor; + $this->collectionPostProcessor = $collectionPostProcessor; } /** @@ -75,7 +84,7 @@ public function getList( /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */ $collection = $this->collectionFactory->create(); - $this->collectionProcessor->process($collection, $searchCriteria, $attributes); + $this->collectionPreProcessor->process($collection, $searchCriteria, $attributes); if (!$isChildSearch) { $visibilityIds = $isSearch @@ -83,18 +92,9 @@ public function getList( : $this->visibility->getVisibleInCatalogIds(); $collection->setVisibility($visibilityIds); } - $collection->load(); - // Methods that perform extra fetches post-load - if (in_array('media_gallery_entries', $attributes)) { - $collection->addMediaGalleryData(); - } - if (in_array('media_gallery', $attributes)) { - $collection->addMediaGalleryData(); - } - if (in_array('options', $attributes)) { - $collection->addOptionsToResult(); - } + $collection->load(); + $this->collectionPostProcessor->process($collection, $attributes); $searchResult = $this->searchResultsFactory->create(); $searchResult->setSearchCriteria($searchCriteria); diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionPostProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionPostProcessor.php new file mode 100644 index 0000000000000..fadf22e7643af --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product/CollectionPostProcessor.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product; + +use Magento\Catalog\Model\ResourceModel\Product\Collection; + +/** + * Processing applied to the collection after load + */ +class CollectionPostProcessor +{ + /** + * Apply processing to loaded product collection + * + * @param Collection $collection + * @param array $attributeNames + * @return Collection + */ + public function process(Collection $collection, array $attributeNames): Collection + { + if (!$collection->isLoaded()) { + $collection->load(); + } + // Methods that perform extra fetches post-load + if (in_array('media_gallery_entries', $attributeNames)) { + $collection->addMediaGalleryData(); + } + if (in_array('media_gallery', $attributeNames)) { + $collection->addMediaGalleryData(); + } + if (in_array('options', $attributeNames)) { + $collection->addOptionsToResult(); + } + + return $collection; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php new file mode 100644 index 0000000000000..8c5fe42c730f6 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider; + +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionPostProcessor; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; +use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; +use Magento\Framework\Api\Search\SearchResultInterface; +use Magento\Framework\Api\SearchCriteriaInterface; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Api\Data\ProductSearchResultsInterfaceFactory; +use Magento\Framework\Api\SearchResultsInterface; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product\CollectionProcessorInterface; + +/** + * Product field data provider for product search, used for GraphQL resolver processing. + */ +class ProductSearch +{ + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @var ProductSearchResultsInterfaceFactory + */ + private $searchResultsFactory; + + /** + * @var CollectionProcessorInterface + */ + private $collectionPreProcessor; + + /** + * @var CollectionPostProcessor + */ + private $collectionPostProcessor; + + /** + * @var SearchResultApplierFactory; + */ + private $searchResultApplierFactory; + + /** + * @param CollectionFactory $collectionFactory + * @param ProductSearchResultsInterfaceFactory $searchResultsFactory + * @param CollectionProcessorInterface $collectionPreProcessor + * @param CollectionPostProcessor $collectionPostProcessor + * @param SearchResultApplierFactory $searchResultsApplierFactory + */ + public function __construct( + CollectionFactory $collectionFactory, + ProductSearchResultsInterfaceFactory $searchResultsFactory, + CollectionProcessorInterface $collectionPreProcessor, + CollectionPostProcessor $collectionPostProcessor, + SearchResultApplierFactory $searchResultsApplierFactory + ) { + $this->collectionFactory = $collectionFactory; + $this->searchResultsFactory = $searchResultsFactory; + $this->collectionPreProcessor = $collectionPreProcessor; + $this->collectionPostProcessor = $collectionPostProcessor; + $this->searchResultApplierFactory = $searchResultsApplierFactory; + } + + /** + * Get list of product data with full data set. Adds eav attributes to result set from passed in array + * + * @param SearchCriteriaInterface $searchCriteria + * @param SearchResultInterface $searchResult + * @param array $attributes + * @return SearchResultsInterface + */ + public function getList( + SearchCriteriaInterface $searchCriteria, + SearchResultsInterface $searchResult, + array $attributes = [] + ): SearchResultsInterface { + /** @var Collection $collection */ + $collection = $this->collectionFactory->create(); + + //Join search results + $this->getSearchResultsApplier($searchResult, $collection, $this->getSortOrderArray($searchCriteria))->apply(); + + $this->collectionPreProcessor->process($collection, $searchCriteria, $attributes); + $collection->load(); + $this->collectionPostProcessor->process($collection, $attributes); + + $searchResult = $this->searchResultsFactory->create(); + $searchResult->setSearchCriteria($searchCriteria); + $searchResult->setItems($collection->getItems()); + $searchResult->setTotalCount($collection->getSize()); + return $searchResult; + } + + /** + * Create searchResultApplier + * + * @param SearchResultInterface $searchResult + * @param Collection $collection + * @param array $orders + * @return SearchResultApplierInterface + */ + private function getSearchResultsApplier( + SearchResultInterface $searchResult, + Collection $collection, + array $orders + ): SearchResultApplierInterface { + return $this->searchResultApplierFactory->create( + [ + 'collection' => $collection, + 'searchResult' => $searchResult, + 'orders' => $orders + ] + ); + } + + /** + * Format sort orders into associative array + * + * E.g. ['field1' => 'DESC', 'field2' => 'ASC", ...] + * + * @param SearchCriteriaInterface $searchCriteria + * @return array + */ + private function getSortOrderArray(SearchCriteriaInterface $searchCriteria) + { + $ordersArray = []; + $sortOrders = $searchCriteria->getSortOrders(); + if (is_array($sortOrders)) { + foreach ($sortOrders as $sortOrder) { + $ordersArray[$sortOrder->getField()] = $sortOrder->getDirection(); + } + } + + return $ordersArray; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php new file mode 100644 index 0000000000000..3912bab05ebbe --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/FieldSelection.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query; + +use GraphQL\Language\AST\SelectionNode; +use Magento\Framework\GraphQl\Query\FieldTranslator; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Extract requested fields from products query + */ +class FieldSelection +{ + /** + * @var FieldTranslator + */ + private $fieldTranslator; + + /** + * @param FieldTranslator $fieldTranslator + */ + public function __construct(FieldTranslator $fieldTranslator) + { + $this->fieldTranslator = $fieldTranslator; + } + + /** + * Get requested fields from products query + * + * @param ResolveInfo $resolveInfo + * @return string[] + */ + public function getProductsFieldSelection(ResolveInfo $resolveInfo): array + { + return $this->getProductFields($resolveInfo); + } + + /** + * Return field names for all requested product fields. + * + * @param ResolveInfo $info + * @return string[] + */ + private function getProductFields(ResolveInfo $info): array + { + $fieldNames = []; + foreach ($info->fieldNodes as $node) { + if ($node->name->value !== 'products') { + continue; + } + foreach ($node->selectionSet->selections as $selection) { + if ($selection->name->value !== 'items') { + continue; + } + $fieldNames[] = $this->collectProductFieldNames($selection, $fieldNames); + } + } + + $fieldNames = array_merge(...$fieldNames); + + return $fieldNames; + } + + /** + * Collect field names for each node in selection + * + * @param SelectionNode $selection + * @param array $fieldNames + * @return array + */ + private function collectProductFieldNames(SelectionNode $selection, array $fieldNames = []): array + { + foreach ($selection->selectionSet->selections as $itemSelection) { + if ($itemSelection->kind === 'InlineFragment') { + foreach ($itemSelection->selectionSet->selections as $inlineSelection) { + if ($inlineSelection->kind === 'InlineFragment') { + continue; + } + $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value); + } + continue; + } + $fieldNames[] = $this->fieldTranslator->translate($itemSelection->name->value); + } + + return $fieldNames; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php index 6771777341281..cc25af44fdfbe 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Filter.php @@ -7,13 +7,11 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query; -use GraphQL\Language\AST\SelectionNode; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\Api\SearchCriteriaInterface; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Product; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory; -use Magento\Framework\GraphQl\Query\FieldTranslator; /** * Retrieve filtered product data based off given search criteria in a format that GraphQL can interpret. @@ -31,31 +29,31 @@ class Filter private $productDataProvider; /** - * @var FieldTranslator + * @var \Magento\Catalog\Model\Layer\Resolver */ - private $fieldTranslator; + private $layerResolver; /** - * @var \Magento\Catalog\Model\Layer\Resolver + * FieldSelection */ - private $layerResolver; + private $fieldSelection; /** * @param SearchResultFactory $searchResultFactory * @param Product $productDataProvider * @param \Magento\Catalog\Model\Layer\Resolver $layerResolver - * @param FieldTranslator $fieldTranslator + * @param FieldSelection $fieldSelection */ public function __construct( SearchResultFactory $searchResultFactory, Product $productDataProvider, \Magento\Catalog\Model\Layer\Resolver $layerResolver, - FieldTranslator $fieldTranslator + FieldSelection $fieldSelection ) { $this->searchResultFactory = $searchResultFactory; $this->productDataProvider = $productDataProvider; - $this->fieldTranslator = $fieldTranslator; $this->layerResolver = $layerResolver; + $this->fieldSelection = $fieldSelection; } /** @@ -71,7 +69,7 @@ public function getResult( ResolveInfo $info, bool $isSearch = false ): SearchResult { - $fields = $this->getProductFields($info); + $fields = $this->fieldSelection->getProductsFieldSelection($info); $products = $this->productDataProvider->getList($searchCriteria, $fields, $isSearch); $productArray = []; /** @var \Magento\Catalog\Model\Product $product */ @@ -87,55 +85,4 @@ public function getResult( ] ); } - - /** - * Return field names for all requested product fields. - * - * @param ResolveInfo $info - * @return string[] - */ - private function getProductFields(ResolveInfo $info) : array - { - $fieldNames = []; - foreach ($info->fieldNodes as $node) { - if ($node->name->value !== 'products') { - continue; - } - foreach ($node->selectionSet->selections as $selection) { - if ($selection->name->value !== 'items') { - continue; - } - $fieldNames[] = $this->collectProductFieldNames($selection, $fieldNames); - } - } - - $fieldNames = array_merge(...$fieldNames); - - return $fieldNames; - } - - /** - * Collect field names for each node in selection - * - * @param SelectionNode $selection - * @param array $fieldNames - * @return array - */ - private function collectProductFieldNames(SelectionNode $selection, array $fieldNames = []): array - { - foreach ($selection->selectionSet->selections as $itemSelection) { - if ($itemSelection->kind === 'InlineFragment') { - foreach ($itemSelection->selectionSet->selections as $inlineSelection) { - if ($inlineSelection->kind === 'InlineFragment') { - continue; - } - $fieldNames[] = $this->fieldTranslator->translate($inlineSelection->name->value); - } - continue; - } - $fieldNames[] = $this->fieldTranslator->translate($itemSelection->name->value); - } - - return $fieldNames; - } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index c7dd2a8b05b79..a6d7f8641bd86 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -7,9 +7,9 @@ namespace Magento\CatalogGraphQl\Model\Resolver\Products\Query; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ProductSearch; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\Api\Search\SearchCriteriaInterface; -use Magento\CatalogGraphQl\Model\Resolver\Products\SearchCriteria\Helper\Filter as FilterHelper; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult; use Magento\CatalogGraphQl\Model\Resolver\Products\SearchResultFactory; use Magento\Search\Api\SearchInterface; @@ -25,26 +25,11 @@ class Search */ private $search; - /** - * @var FilterHelper - */ - private $filterHelper; - - /** - * @var Filter - */ - private $filterQuery; - /** * @var SearchResultFactory */ private $searchResultFactory; - /** - * @var \Magento\Framework\EntityManager\MetadataPool - */ - private $metadataPool; - /** * @var \Magento\Search\Model\Search\PageSizeProvider */ @@ -55,31 +40,38 @@ class Search */ private $searchCriteriaFactory; + /** + * @var FieldSelection + */ + private $fieldSelection; + + /** + * @var ProductSearch + */ + private $productsProvider; + /** * @param SearchInterface $search - * @param FilterHelper $filterHelper - * @param Filter $filterQuery * @param SearchResultFactory $searchResultFactory - * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param \Magento\Search\Model\Search\PageSizeProvider $pageSize * @param SearchCriteriaInterfaceFactory $searchCriteriaFactory + * @param FieldSelection $fieldSelection + * @param ProductSearch $productsProvider */ public function __construct( SearchInterface $search, - FilterHelper $filterHelper, - Filter $filterQuery, SearchResultFactory $searchResultFactory, - \Magento\Framework\EntityManager\MetadataPool $metadataPool, \Magento\Search\Model\Search\PageSizeProvider $pageSize, - SearchCriteriaInterfaceFactory $searchCriteriaFactory + SearchCriteriaInterfaceFactory $searchCriteriaFactory, + FieldSelection $fieldSelection, + ProductSearch $productsProvider ) { $this->search = $search; - $this->filterHelper = $filterHelper; - $this->filterQuery = $filterQuery; $this->searchResultFactory = $searchResultFactory; - $this->metadataPool = $metadataPool; $this->pageSizeProvider = $pageSize; $this->searchCriteriaFactory = $searchCriteriaFactory; + $this->fieldSelection = $fieldSelection; + $this->productsProvider = $productsProvider; } /** @@ -87,20 +79,15 @@ public function __construct( * * @param SearchCriteriaInterface $searchCriteria * @param ResolveInfo $info - * @param array $args * @return SearchResult * @throws \Exception */ public function getResult( SearchCriteriaInterface $searchCriteria, - ResolveInfo $info, - array $args = [] + ResolveInfo $info ): SearchResult { - $idField = $this->metadataPool->getMetadata( - \Magento\Catalog\Api\Data\ProductInterface::class - )->getIdentifierField(); + $queryFields = $this->fieldSelection->getProductsFieldSelection($info); - $isSearch = isset($args['search']); $realPageSize = $searchCriteria->getPageSize(); $realCurrentPage = $searchCriteria->getCurrentPage(); // Current page must be set to 0 and page size to max for search to grab all ID's as temporary workaround @@ -109,35 +96,32 @@ public function getResult( $searchCriteria->setCurrentPage(0); $itemsResults = $this->search->search($searchCriteria); - $ids = []; - $searchIds = []; - foreach ($itemsResults->getItems() as $item) { - $ids[$item->getId()] = null; - $searchIds[] = $item->getId(); - } - - $searchCriteriaIds = $this->searchCriteriaFactory->create(); - $filter = $this->filterHelper->generate($idField, 'in', $searchIds); - $searchCriteriaIds = $this->filterHelper->add($searchCriteriaIds, $filter); - $searchCriteriaIds->setSortOrders($searchCriteria->getSortOrders()); - $searchCriteriaIds->setPageSize($realPageSize); - $searchCriteriaIds->setCurrentPage($realCurrentPage); + //Create copy of search criteria without conditions (conditions will be applied by joining search result) + $searchCriteriaCopy = $this->searchCriteriaFactory->create() + ->setSortOrders($searchCriteria->getSortOrders()) + ->setPageSize($realPageSize) + ->setCurrentPage($realCurrentPage); - $searchResult = $this->filterQuery->getResult($searchCriteriaIds, $info, $isSearch); + $searchResults = $this->productsProvider->getList($searchCriteriaCopy, $itemsResults, $queryFields); - $searchCriteria->setPageSize($realPageSize); - $searchCriteria->setCurrentPage($realCurrentPage); //possible division by 0 if ($realPageSize) { - $maxPages = (int)ceil($searchResult->getTotalCount() / $realPageSize); + $maxPages = (int)ceil($searchResults->getTotalCount() / $realPageSize); } else { $maxPages = 0; } + $productArray = []; + /** @var \Magento\Catalog\Model\Product $product */ + foreach ($searchResults->getItems() as $product) { + $productArray[$product->getId()] = $product->getData(); + $productArray[$product->getId()]['model'] = $product; + } + return $this->searchResultFactory->create( [ - 'totalCount' => $searchResult->getTotalCount(), - 'productsSearchResult' => $searchResult->getProductsSearchResult(), + 'totalCount' => $searchResults->getTotalCount(), + 'productsSearchResult' => $productArray, 'searchAggregation' => $itemsResults->getAggregations(), 'pageSize' => $realPageSize, 'currentPage' => $realCurrentPage, diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index aadd43fafbd7f..9d8b6f69f5842 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -374,6 +374,7 @@ input ProductSortInput @deprecated(reason: "Attributes used in this input are ha input ProductAttributeSortInput @doc(description: "ProductAttributeSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order. It's possible to sort products using searchable attributes with enabled 'Use in Filter Options' option") { position: SortEnum @doc(description: "The position of products") + relevance: SortEnum @doc(description: "The search relevance score (default)") } type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product.") { From 3c38665d00c4f14da6d8dbbd0b652249298fdd44 Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Tue, 27 Aug 2019 09:47:05 -0500 Subject: [PATCH 448/841] MC-18945: Reading deprecated annotation in schema - Fixed the reader files --- .../GraphQlReader/MetaReader/FieldMetaReader.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php index c147cf4d1c8ed..217a233eae20c 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php @@ -99,7 +99,7 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra $result['arguments'][$argumentName]['defaultValue'] = $argumentMeta->defaultValue; } $typeMeta = $argumentMeta->getType(); - $result['arguments'][$argumentName] = $this->argumentMetaType($typeMeta, $argumentMeta); + $result['arguments'][$argumentName] = $this->argumentMetaType($typeMeta, $argumentMeta, $result); if ($this->docReader->read($argumentMeta->astNode->directives)) { $result['arguments'][$argumentName]['description'] = @@ -117,18 +117,17 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra /** * Get the argumentMetaType result array * - * @param array $typeMeta - * @param array $argumentMeta + * @param \GraphQL\Type\Definition\InputType $typeMeta + * @param \GraphQL\Type\Definition\FieldArgument $argumentMeta + * @param array $result * @return array */ private function argumentMetaType( \GraphQL\Type\Definition\InputType $typeMeta, - \GraphQL\Type\Definition\FieldArgument $argumentMeta - ) { + \GraphQL\Type\Definition\FieldArgument $argumentMeta, + $result + ) : array { $argumentName = $argumentMeta->name; - $result['arguments'][$argumentName] = [ - 'name' => $argumentName, - ]; $result['arguments'][$argumentName] = array_merge( $result['arguments'][$argumentName], $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER) From 73b96122f3df5c99dfc25a809a725e54e2039998 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Tue, 27 Aug 2019 09:53:11 -0500 Subject: [PATCH 449/841] Update sidebar.js --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index 3a7fb17c31b8f..82170929d8e73 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -261,7 +261,7 @@ define([ $(document).trigger('ajax:removeFromCart', { productIds: [productData['product_id']] }); - if (window.location.href === this.shoppingCartUrl) { + if (window.location.href.indexOf(this.shoppingCartUrl) === 0) { window.location.reload(); } } From 28792f6880bf2404eb7ccc40ec172d03fc2b5782 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Tue, 27 Aug 2019 10:02:15 -0500 Subject: [PATCH 450/841] MC-19432: REST API returns 404 error instead of 400 --- .../Quote/Model/Quote/Item/Repository.php | 63 +++++------ .../Unit/Model/Quote/Item/RepositoryTest.php | 100 +++++++++--------- .../Webapi/ServiceInputProcessor.php | 21 ---- 3 files changed, 82 insertions(+), 102 deletions(-) diff --git a/app/code/Magento/Quote/Model/Quote/Item/Repository.php b/app/code/Magento/Quote/Model/Quote/Item/Repository.php index 1fb0a2d7107f1..2b4852251bca2 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Repository.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Repository.php @@ -1,34 +1,40 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Quote\Model\Quote\Item; -use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Quote\Api\CartItemRepositoryInterface; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartItemInterfaceFactory; -class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface +/** + * Repository for quote item. + */ +class Repository implements CartItemRepositoryInterface { /** * Quote repository. * - * @var \Magento\Quote\Api\CartRepositoryInterface + * @var CartRepositoryInterface */ protected $quoteRepository; /** * Product repository. * - * @var \Magento\Catalog\Api\ProductRepositoryInterface + * @var ProductRepositoryInterface */ protected $productRepository; /** - * @var \Magento\Quote\Api\Data\CartItemInterfaceFactory + * @var CartItemInterfaceFactory */ protected $itemDataFactory; @@ -43,25 +49,28 @@ class Repository implements \Magento\Quote\Api\CartItemRepositoryInterface private $cartItemOptionsProcessor; /** - * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository - * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository - * @param \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory + * @param CartRepositoryInterface $quoteRepository + * @param ProductRepositoryInterface $productRepository + * @param CartItemInterfaceFactory $itemDataFactory + * @param CartItemOptionsProcessor $cartItemOptionsProcessor * @param CartItemProcessorInterface[] $cartItemProcessors */ public function __construct( - \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, - \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, - \Magento\Quote\Api\Data\CartItemInterfaceFactory $itemDataFactory, + CartRepositoryInterface $quoteRepository, + ProductRepositoryInterface $productRepository, + CartItemInterfaceFactory $itemDataFactory, + CartItemOptionsProcessor $cartItemOptionsProcessor, array $cartItemProcessors = [] ) { $this->quoteRepository = $quoteRepository; $this->productRepository = $productRepository; $this->itemDataFactory = $itemDataFactory; + $this->cartItemOptionsProcessor = $cartItemOptionsProcessor; $this->cartItemProcessors = $cartItemProcessors; } /** - * {@inheritdoc} + * @inheritdoc */ public function getList($cartId) { @@ -71,21 +80,26 @@ public function getList($cartId) /** @var \Magento\Quote\Model\Quote\Item $item */ foreach ($quote->getAllVisibleItems() as $item) { - $item = $this->getCartItemOptionsProcessor()->addProductOptions($item->getProductType(), $item); - $output[] = $this->getCartItemOptionsProcessor()->applyCustomOptions($item); + $item = $this->cartItemOptionsProcessor->addProductOptions($item->getProductType(), $item); + $output[] = $this->cartItemOptionsProcessor->applyCustomOptions($item); } return $output; } /** - * {@inheritdoc} + * @inheritdoc */ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) { /** @var \Magento\Quote\Model\Quote $quote */ $cartId = $cartItem->getQuoteId(); - $quote = $this->quoteRepository->getActive($cartId); + if (!$cartId) { + throw new InputException( + __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'cartId']) + ); + } + $quote = $this->quoteRepository->getActive($cartId); $quoteItems = $quote->getItems(); $quoteItems[] = $cartItem; $quote->setItems($quoteItems); @@ -95,7 +109,7 @@ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) } /** - * {@inheritdoc} + * @inheritdoc */ public function deleteById($cartId, $itemId) { @@ -116,17 +130,4 @@ public function deleteById($cartId, $itemId) return true; } - - /** - * @return CartItemOptionsProcessor - * @deprecated 100.1.0 - */ - private function getCartItemOptionsProcessor() - { - if (!$this->cartItemOptionsProcessor instanceof CartItemOptionsProcessor) { - $this->cartItemOptionsProcessor = ObjectManager::getInstance()->get(CartItemOptionsProcessor::class); - } - - return $this->cartItemOptionsProcessor; - } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php index 4ecd8b021d7f0..dd4f2f6e7470c 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php @@ -7,64 +7,75 @@ namespace Magento\Quote\Test\Unit\Model\Quote\Item; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\CustomOptions\CustomOptionProcessor; +use Magento\Catalog\Model\Product; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartItemInterfaceFactory; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; +use Magento\Quote\Model\Quote\Item; +use Magento\Quote\Model\Quote\Item\CartItemOptionsProcessor; +use Magento\Quote\Model\Quote\Item\Repository; +use PHPUnit\Framework\MockObject\MockObject; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class RepositoryTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @var Repository */ - private $objectManager; + private $repository; /** - * @var \Magento\Quote\Api\CartItemRepositoryInterface + * @var CartRepositoryInterface|MockObject */ - protected $repository; + private $quoteRepositoryMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ProductRepositoryInterface|MockObject */ - protected $quoteRepositoryMock; + private $productRepositoryMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ - protected $productRepositoryMock; + private $itemMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ - protected $itemMock; + private $quoteMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ - protected $quoteMock; + private $productMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ - protected $productMock; + private $quoteItemMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var CartItemInterfaceFactory|MockObject */ - protected $quoteItemMock; + private $itemDataFactoryMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var CustomOptionProcessor|MockObject */ - protected $itemDataFactoryMock; - - /** @var \Magento\Catalog\Model\CustomOptions\CustomOptionProcessor|\PHPUnit_Framework_MockObject_MockObject */ - protected $customOptionProcessor; + private $customOptionProcessor; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $shippingAddressMock; + /** + * @var Address|MockObject + */ + private $shippingAddressMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var CartItemOptionsProcessor|MockObject */ private $optionsProcessorMock; @@ -73,40 +84,29 @@ class RepositoryTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class); - $this->productRepositoryMock = $this->createMock(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $this->itemDataFactoryMock = - $this->createPartialMock(\Magento\Quote\Api\Data\CartItemInterfaceFactory::class, ['create']); - $this->itemMock = $this->createMock(\Magento\Quote\Model\Quote\Item::class); - $this->quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->productMock = $this->createMock(\Magento\Catalog\Model\Product::class); + $this->quoteRepositoryMock = $this->createMock(CartRepositoryInterface::class); + $this->productRepositoryMock = $this->createMock(ProductRepositoryInterface::class); + $this->itemDataFactoryMock = $this->createPartialMock(CartItemInterfaceFactory::class, ['create']); + $this->itemMock = $this->createMock(Item::class); + $this->quoteMock = $this->createMock(Quote::class); + $this->productMock = $this->createMock(Product::class); $methods = ['getId', 'getSku', 'getQty', 'setData', '__wakeUp', 'getProduct', 'addProduct']; $this->quoteItemMock = - $this->createPartialMock(\Magento\Quote\Model\Quote\Item::class, $methods); - $this->customOptionProcessor = $this->createMock( - \Magento\Catalog\Model\CustomOptions\CustomOptionProcessor::class - ); + $this->createPartialMock(Item::class, $methods); + $this->customOptionProcessor = $this->createMock(CustomOptionProcessor::class); $this->shippingAddressMock = $this->createPartialMock( - \Magento\Quote\Model\Quote\Address::class, + Address::class, ['setCollectShippingRates'] ); + $this->optionsProcessorMock = $this->createMock(CartItemOptionsProcessor::class); - $this->optionsProcessorMock = $this->createMock( - \Magento\Quote\Model\Quote\Item\CartItemOptionsProcessor::class - ); - - $this->repository = new \Magento\Quote\Model\Quote\Item\Repository( + $this->repository = new Repository( $this->quoteRepositoryMock, $this->productRepositoryMock, $this->itemDataFactoryMock, + $this->optionsProcessorMock, ['custom_options' => $this->customOptionProcessor] ); - $this->objectManager->setBackwardCompatibleProperty( - $this->repository, - 'cartItemOptionsProcessor', - $this->optionsProcessorMock - ); } /** @@ -118,7 +118,7 @@ public function testSave() $itemId = 20; $quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Quote::class, + Quote::class, ['getItems', 'setItems', 'collectTotals', 'getLastAddedItem'] ); @@ -197,11 +197,11 @@ public function testDeleteWithCouldNotSaveException() public function testGetList() { $productType = 'type'; - $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); + $quoteMock = $this->createMock(Quote::class); $this->quoteRepositoryMock->expects($this->once())->method('getActive') ->with(33) ->will($this->returnValue($quoteMock)); - $itemMock = $this->createMock(\Magento\Quote\Model\Quote\Item::class); + $itemMock = $this->createMock(Item::class); $quoteMock->expects($this->once())->method('getAllVisibleItems')->will($this->returnValue([$itemMock])); $itemMock->expects($this->once())->method('getProductType')->willReturn($productType); diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index f9995cbc6bf00..c253a400bed93 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -243,27 +243,6 @@ protected function _createFromArray($className, $data) $className = substr($className, 0, -strlen('Interface')); } - $typeName = $this->typeProcessor->register($className); - $typeData = $this->typeProcessor->getTypeData($typeName); - $requiredFields = array_keys( - array_filter( - $typeData['parameters'], - function (array $fieldData) { - return $fieldData['required']; - } - ) - ); - $camelCaseData = array_map( - function (string $value) { - return SimpleDataObjectConverter::snakeCaseToCamelCase($value); - }, - array_keys($data) - ); - $missedRequiredFields = array_diff($requiredFields, $camelCaseData); - if (!empty($missedRequiredFields)) { - $this->processInputError($missedRequiredFields); - } - // Primary method: assign to constructor parameters $constructorArgs = $this->getConstructorData($className, $data); $object = $this->objectManager->create($className, $constructorArgs); From 4d6a89c6b97deb4e21c23a944288004a367505bc Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 27 Aug 2019 10:10:49 -0500 Subject: [PATCH 451/841] MC-19572: Fix filtering by attribute of type select/multiselect using filter input type in - fix unit test --- .../Adapter/Mysql/Filter/PreprocessorTest.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php index 7e3de7534e8c4..a79ffcc33cabe 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php @@ -129,7 +129,7 @@ protected function setUp() ->getMock(); $this->connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) ->disableOriginalConstructor() - ->setMethods(['select', 'getIfNullSql', 'quote']) + ->setMethods(['select', 'getIfNullSql', 'quote', 'quoteInto']) ->getMockForAbstractClass(); $this->select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() @@ -222,9 +222,10 @@ public function testProcessPrice() public function processCategoryIdsDataProvider() { return [ - ['5', 'category_ids_index.category_id = 5'], - [3, 'category_ids_index.category_id = 3'], - ["' and 1 = 0", 'category_ids_index.category_id = 0'], + ['5', "category_ids_index.category_id in ('5')"], + [3, "category_ids_index.category_id in (3)"], + ["' and 1 = 0", "category_ids_index.category_id in ('\' and 1 = 0')"], + [['5', '10'], "category_ids_index.category_id in ('5', '10')"] ]; } @@ -251,6 +252,12 @@ public function testProcessCategoryIds($categoryId, $expectedResult) ->with(\Magento\Catalog\Model\Product::ENTITY, 'category_ids') ->will($this->returnValue($this->attribute)); + $this->connection + ->expects($this->once()) + ->method('quoteInto') + ->with('category_ids_index.category_id in (?)', $categoryId) + ->willReturn($expectedResult); + $actualResult = $this->target->process($this->filter, $isNegation, $query); $this->assertSame($expectedResult, $this->removeWhitespaces($actualResult)); } From 1118db4f55f66e02f59fcf45ed003d6125310f01 Mon Sep 17 00:00:00 2001 From: Olga Kopylova <kopylova@adobe.com> Date: Tue, 27 Aug 2019 11:18:39 -0500 Subject: [PATCH 452/841] Fixed Igor's username --- .github/CODEOWNERS | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 84b29393699c4..00763eb56e0c6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,12 +10,12 @@ /lib/internal/Magento/Framework/Notification/ @buskamuza /lib/internal/Magento/Framework/Session/ @buskamuza /lib/internal/Magento/Framework/Url/ @buskamuza -/app/code/Magento/Cms/ @Melnykov -/app/code/Magento/CmsUrlRewrite/ @Melnykov -/app/code/Magento/Contact/ @Melnykov -/app/code/Magento/Email/ @Melnykov -/app/code/Magento/Variable/ @Melnykov -/app/code/Magento/Widget/ @Melnykov +/app/code/Magento/Cms/ @melnikovi +/app/code/Magento/CmsUrlRewrite/ @melnikovi +/app/code/Magento/Contact/ @melnikovi +/app/code/Magento/Email/ @melnikovi +/app/code/Magento/Variable/ @melnikovi +/app/code/Magento/Widget/ @melnikovi /lib/internal/Magento/Framework/Cache/ @kokoc /app/code/Magento/CacheInvalidate/ @kokoc /app/code/Magento/CatalogInventory/ @tariqjawed83 @maghamed @@ -92,22 +92,22 @@ /lib/internal/Magento/Framework/Css/ @DrewML /lib/internal/Magento/Framework/Option/ @DrewML /lib/internal/Magento/Framework/RequireJs/ @DrewML -/lib/internal/Magento/Framework/View/ @Melnykov +/lib/internal/Magento/Framework/View/ @melnikovi /dev/tests/js/ @DrewML /app/code/Magento/RequireJs/ @DrewML -/app/code/Magento/Theme/ @Melnykov -/app/code/Magento/Ui/ @Melnykov -/lib/internal/Magento/Framework/Intl/ @Melnykov -/lib/internal/Magento/Framework/Locale/ @Melnykov -/lib/internal/Magento/Framework/Phrase/ @Melnykov -/lib/internal/Magento/Framework/Translate/ @Melnykov -/app/code/Magento/Translation/ @Melnykov +/app/code/Magento/Theme/ @melnikovi +/app/code/Magento/Ui/ @melnikovi +/lib/internal/Magento/Framework/Intl/ @melnikovi +/lib/internal/Magento/Framework/Locale/ @melnikovi +/lib/internal/Magento/Framework/Phrase/ @melnikovi +/lib/internal/Magento/Framework/Translate/ @melnikovi +/app/code/Magento/Translation/ @melnikovi /app/code/Magento/ImportExport/ @akaplya -/app/code/Magento/GoogleAdwords/ @buskamuza @Melnykov -/app/code/Magento/Newsletter/ @buskamuza @Melnykov -/app/code/Magento/ProductAlert/ @buskamuza @Melnykov -/app/code/Magento/Rss/ @buskamuza @Melnykov -/app/code/Magento/SendFriend/ @buskamuza @Melnykov +/app/code/Magento/GoogleAdwords/ @buskamuza @melnikovi +/app/code/Magento/Newsletter/ @buskamuza @melnikovi +/app/code/Magento/ProductAlert/ @buskamuza @melnikovi +/app/code/Magento/Rss/ @buskamuza @melnikovi +/app/code/Magento/SendFriend/ @buskamuza @melnikovi /app/code/Magento/Marketplace/ @buskamuza /app/code/Magento/MediaStorage/ @buskamuza /lib/internal/Magento/Framework/Amqp/ @tariqjawed83 @paliarush @@ -186,10 +186,10 @@ /dev/tests/api-functional/ @paliarush /app/code/Magento/UrlRewrite/ @kokoc /lib/internal/Magento/Framework/Image/ @buskamuza -/lib/internal/Magento/Framework/Mail/ @Melnykov -/lib/internal/Magento/Framework/Filter/ @Melnykov -/lib/internal/Magento/Framework/Validation/ @Melnykov -/lib/internal/Magento/Framework/Validator/ @Melnykov +/lib/internal/Magento/Framework/Mail/ @melnikovi +/lib/internal/Magento/Framework/Filter/ @melnikovi +/lib/internal/Magento/Framework/Validation/ @melnikovi +/lib/internal/Magento/Framework/Validator/ @melnikovi /lib/internal/Magento/Framework/Api/ @paliarush /lib/internal/Magento/Framework/GraphQL/ @paliarush /lib/internal/Magento/Framework/Oauth/ @paliarush From 1d160541b9096b3a39cfffba3fd0e5b30d155fe2 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Tue, 27 Aug 2019 11:20:50 -0500 Subject: [PATCH 453/841] magento/graphql-ce#685: Extract logic related to customer from DownloadableGraphQl module into new one - removed composer dependency because of DependencyTest bug MC-19635 --- app/code/Magento/CustomerDownloadableGraphQl/composer.json | 2 -- app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml | 2 ++ app/code/Magento/DownloadableGraphQl/composer.json | 1 - app/code/Magento/DownloadableGraphQl/etc/module.xml | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CustomerDownloadableGraphQl/composer.json b/app/code/Magento/CustomerDownloadableGraphQl/composer.json index 84038c6b7a883..418d2b57b8b42 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/composer.json +++ b/app/code/Magento/CustomerDownloadableGraphQl/composer.json @@ -4,8 +4,6 @@ "type": "magento2-module", "require": { "php": "~7.1.3||~7.2.0||~7.3.0", - "magento/module-catalog": "*", - "magento/module-downloadable": "*", "magento/module-downloadable-graph-ql": "*", "magento/module-graph-ql": "*", "magento/framework": "*" diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml index fe513e7816ce7..6dc201eb00ef9 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml +++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml @@ -8,6 +8,8 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_CustomerDownloadableGraphQl" > <sequence> + <module name="Magento_Catalog"/> + <module name="Magento_Downloadable"/> <module name="Magento_GraphQl"/> <module name="Magento_CatalogGraphQl"/> <module name="Magento_DownloadableGraphQl"/> diff --git a/app/code/Magento/DownloadableGraphQl/composer.json b/app/code/Magento/DownloadableGraphQl/composer.json index 947b4001a5da5..e2e1098031766 100644 --- a/app/code/Magento/DownloadableGraphQl/composer.json +++ b/app/code/Magento/DownloadableGraphQl/composer.json @@ -6,7 +6,6 @@ "php": "~7.1.3||~7.2.0||~7.3.0", "magento/module-catalog": "*", "magento/module-downloadable": "*", - "magento/module-graph-ql": "*", "magento/module-quote": "*", "magento/module-quote-graph-ql": "*", "magento/framework": "*" diff --git a/app/code/Magento/DownloadableGraphQl/etc/module.xml b/app/code/Magento/DownloadableGraphQl/etc/module.xml index c06df1cf92d2f..e6158fdbe64eb 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/module.xml +++ b/app/code/Magento/DownloadableGraphQl/etc/module.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_DownloadableGraphQl" > <sequence> + <module name="Magento_GraphQl"/> <module name="Magento_Catalog"/> <module name="Magento_CatalogGraphQl"/> <module name="Magento_QuoteGraphQl"/> From 86edb008f6003e3bf4011ae1cf4cb94977ac2c4c Mon Sep 17 00:00:00 2001 From: Anusha Vattam <avattam@adobe.com> Date: Tue, 27 Aug 2019 11:49:12 -0500 Subject: [PATCH 454/841] MC-18945: Reading deprecated annotation in schema - Addressed the review comments from the api-tests --- .../GraphQl/IntrospectionQueryTest.php | 79 +++++++++---------- .../GraphQl/Config/Element/Field.php | 7 +- .../GraphQl/Config/Element/Input.php | 2 +- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php index 9cfe3140efcf5..69bcc73dd27a1 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php @@ -53,7 +53,6 @@ public function testIntrospectionQuery() defaultValue } QUERY; - $this->assertArrayHasKey('__schema', $this->graphQlQuery($query)); } @@ -92,62 +91,56 @@ enumValues(includeDeprecated: true) { } QUERY; - - $this->assertArrayHasKey('__schema', $this->graphQlQuery($query)); $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('__schema', $response); $schemaResponseFields = $response['__schema']['types']; $enumValueReasonArray = $this->getEnumValueDeprecatedReason($schemaResponseFields); $fieldsValueReasonArray = $this->getFieldsValueDeprecatedReason($schemaResponseFields); $expectedOutput = require __DIR__ . '/_files/schema_response_sdl_deprecated_annotation.php'; // checking field values deprecated reason - if (!empty($fieldsValueReasonArray)) { - $fieldDeprecatedReason = []; - $fieldsArray = $expectedOutput[0]['fields']; - foreach ($fieldsArray as $field) { - if ($field['isDeprecated'] === true) { - $fieldDeprecatedReason [] = $field['deprecationReason']; - } + $fieldDeprecatedReason = []; + $fieldsArray = $expectedOutput[0]['fields']; + foreach ($fieldsArray as $field) { + if ($field['isDeprecated'] === true) { + $fieldDeprecatedReason [] = $field['deprecationReason']; } - $this->assertNotEmpty($fieldDeprecatedReason); - $this->assertContains( - 'Symbol was missed. Use `default_display_currency_code`.', - $fieldDeprecatedReason - ); - - $this->assertContains( - 'Symbol was missed. Use `default_display_currency_code`.', - $fieldsValueReasonArray - ); + } + $this->assertNotEmpty($fieldDeprecatedReason); + $this->assertContains( + 'Symbol was missed. Use `default_display_currency_code`.', + $fieldDeprecatedReason + ); - $this->assertNotEmpty( - array_intersect($fieldDeprecatedReason, $fieldsValueReasonArray) - ); + $this->assertContains( + 'Symbol was missed. Use `default_display_currency_code`.', + $fieldsValueReasonArray + ); - } + $this->assertNotEmpty( + array_intersect($fieldDeprecatedReason, $fieldsValueReasonArray) + ); // checking enum values deprecated reason - if (!empty($enumValueReasonArray)) { - $enumValueDeprecatedReason = []; - $enumValuesArray = $expectedOutput[1]['enumValues']; - foreach ($enumValuesArray as $enumValue) { - if ($enumValue['isDeprecated'] === true) { - $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; - } + $enumValueDeprecatedReason = []; + $enumValuesArray = $expectedOutput[1]['enumValues']; + foreach ($enumValuesArray as $enumValue) { + if ($enumValue['isDeprecated'] === true) { + $enumValueDeprecatedReason [] = $enumValue['deprecationReason']; } - $this->assertNotEmpty($enumValueDeprecatedReason); - $this->assertContains( - '`sample_url` serves to get the downloadable sample', - $enumValueDeprecatedReason - ); - $this->assertContains( - '`sample_url` serves to get the downloadable sample', - $enumValueReasonArray - ); - $this->assertNotEmpty( - array_intersect($enumValueDeprecatedReason, $enumValueReasonArray) - ); } + $this->assertNotEmpty($enumValueDeprecatedReason); + $this->assertContains( + '`sample_url` serves to get the downloadable sample', + $enumValueDeprecatedReason + ); + $this->assertContains( + '`sample_url` serves to get the downloadable sample', + $enumValueReasonArray + ); + $this->assertNotEmpty( + array_intersect($enumValueDeprecatedReason, $enumValueReasonArray) + ); } /** diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php index a01749bebe694..0b1b8ae3da31b 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php @@ -165,16 +165,17 @@ public function getDescription() : string } /** - * Return the cache + * Return the cache tag for the field. * - * @return array + * @return array|null */ public function getCache() : array { return $this->cache; } + /** - * Return the deprecated + * Return the deprecated annotation for the field * * @return array */ diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php index 1dc8f5a2e779a..3ebad28f7b308 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php @@ -81,7 +81,7 @@ public function getDescription(): string } /** - * Return the deprecated + * Return the deprecated annotation for the input * * @return array */ From 865566d79282333cc6f5c75f2730b7ffe032e35f Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 27 Aug 2019 12:05:42 -0500 Subject: [PATCH 455/841] MC-19618: Update schema for layered navigation output - Schema changes, implementation and static fixes --- .../Category/Query/CategoryAttributeQuery.php | 6 +- .../DataProvider/CategoryAttributesMapper.php | 15 ++-- .../LayeredNavigation/Builder/Attribute.php | 58 +++++----------- .../LayeredNavigation/Builder/Category.php | 49 ++++--------- .../Builder/Formatter/LayerFormatter.php | 48 +++++++++++++ .../LayeredNavigation/Builder/Price.php | 53 +++++---------- .../Model/Resolver/Aggregations.php | 68 +++++++++++++++++++ .../Model/Resolver/LayerFilters.php | 25 +------ .../CatalogGraphQl/etc/schema.graphqls | 16 ++++- 9 files changed, 194 insertions(+), 144 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Formatter/LayerFormatter.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/Aggregations.php diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php b/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php index 0b796c1457254..e3dfa38c78258 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Category/Query/CategoryAttributeQuery.php @@ -49,9 +49,11 @@ public function getQuery(array $categoryIds, array $categoryAttributes, int $sto { $categoryAttributes = \array_merge($categoryAttributes, self::$requiredAttributes); - $attributeQuery = $this->attributeQueryFactory->create([ + $attributeQuery = $this->attributeQueryFactory->create( + [ 'entityType' => CategoryInterface::class - ]); + ] + ); return $attributeQuery->getQuery($categoryIds, $categoryAttributes, $storeId); } diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php b/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php index 1f8aa38d5b939..ea3c0b608d212 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/CategoryAttributesMapper.php @@ -65,12 +65,15 @@ private function formatAttributes(array $attributes): array $arrayTypeAttributes = $this->getFieldsOfArrayType(); return $arrayTypeAttributes - ? array_map(function ($data) use ($arrayTypeAttributes) { - foreach ($arrayTypeAttributes as $attributeCode) { - $data[$attributeCode] = $this->valueToArray($data[$attributeCode] ?? null); - } - return $data; - }, $attributes) + ? array_map( + function ($data) use ($arrayTypeAttributes) { + foreach ($arrayTypeAttributes as $attributeCode) { + $data[$attributeCode] = $this->valueToArray($data[$attributeCode] ?? null); + } + return $data; + }, + $attributes + ) : $attributes; } diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php index 82d167e323fc8..89df1be8d59f2 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php @@ -12,6 +12,7 @@ use Magento\Framework\Api\Search\AggregationInterface; use Magento\Framework\Api\Search\AggregationValueInterface; use Magento\Framework\Api\Search\BucketInterface; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter\LayerFormatter; /** * @inheritdoc @@ -35,6 +36,11 @@ class Attribute implements LayerBuilderInterface */ private $attributeOptionProvider; + /** + * @var LayerFormatter + */ + private $layerFormatter; + /** * @var array */ @@ -45,13 +51,16 @@ class Attribute implements LayerBuilderInterface /** * @param AttributeOptionProvider $attributeOptionProvider - * @param array $bucketNameFilter List with buckets name to be removed from filter + * @param LayerFormatter $layerFormatter + * @param array $bucketNameFilter */ public function __construct( AttributeOptionProvider $attributeOptionProvider, + LayerFormatter $layerFormatter, $bucketNameFilter = [] ) { $this->attributeOptionProvider = $attributeOptionProvider; + $this->layerFormatter = $layerFormatter; $this->bucketNameFilter = \array_merge($this->bucketNameFilter, $bucketNameFilter); } @@ -71,7 +80,7 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array $attributeCode = \preg_replace('~_bucket$~', '', $bucketName); $attribute = $attributeOptions[$attributeCode] ?? []; - $result[$bucketName] = $this->buildLayer( + $result[$bucketName] = $this->layerFormatter->buildLayer( $attribute['attribute_label'] ?? $bucketName, \count($bucket->getValues()), $attribute['attribute_code'] ?? $bucketName @@ -79,7 +88,7 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array foreach ($bucket->getValues() as $value) { $metrics = $value->getMetrics(); - $result[$bucketName]['filter_items'][] = $this->buildItem( + $result[$bucketName]['options'][] = $this->layerFormatter->buildItem( $attribute['options'][$metrics['value']] ?? $metrics['value'], $metrics['value'], $metrics['count'] @@ -109,40 +118,6 @@ private function getAttributeBuckets(AggregationInterface $aggregation) } } - /** - * Format layer data - * - * @param string $layerName - * @param string $itemsCount - * @param string $requestName - * @return array - */ - private function buildLayer($layerName, $itemsCount, $requestName): array - { - return [ - 'name' => $layerName, - 'filter_items_count' => $itemsCount, - 'request_var' => $requestName - ]; - } - - /** - * Format layer item data - * - * @param string $label - * @param string|int $value - * @param string|int $count - * @return array - */ - private function buildItem($label, $value, $count): array - { - return [ - 'label' => $label, - 'value_string' => $value, - 'items_count' => $count, - ]; - } - /** * Check that bucket contains data * @@ -165,9 +140,12 @@ private function getAttributeOptions(AggregationInterface $aggregation): array { $attributeOptionIds = []; foreach ($this->getAttributeBuckets($aggregation) as $bucket) { - $attributeOptionIds[] = \array_map(function (AggregationValueInterface $value) { - return $value->getValue(); - }, $bucket->getValues()); + $attributeOptionIds[] = \array_map( + function (AggregationValueInterface $value) { + return $value->getValue(); + }, + $bucket->getValues() + ); } if (!$attributeOptionIds) { diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php index c726f66e8c926..97644328abc2a 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php @@ -15,6 +15,7 @@ use Magento\Framework\Api\Search\AggregationValueInterface; use Magento\Framework\Api\Search\BucketInterface; use Magento\Framework\App\ResourceConnection; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter\LayerFormatter; /** * @inheritdoc @@ -56,22 +57,30 @@ class Category implements LayerBuilderInterface */ private $rootCategoryProvider; + /** + * @var LayerFormatter + */ + private $layerFormatter; + /** * @param CategoryAttributeQuery $categoryAttributeQuery * @param CategoryAttributesMapper $attributesMapper * @param RootCategoryProvider $rootCategoryProvider * @param ResourceConnection $resourceConnection + * @param LayerFormatter $layerFormatter */ public function __construct( CategoryAttributeQuery $categoryAttributeQuery, CategoryAttributesMapper $attributesMapper, RootCategoryProvider $rootCategoryProvider, - ResourceConnection $resourceConnection + ResourceConnection $resourceConnection, + LayerFormatter $layerFormatter ) { $this->categoryAttributeQuery = $categoryAttributeQuery; $this->attributesMapper = $attributesMapper; $this->resourceConnection = $resourceConnection; $this->rootCategoryProvider = $rootCategoryProvider; + $this->layerFormatter = $layerFormatter; } /** @@ -105,7 +114,7 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array return []; } - $result = $this->buildLayer( + $result = $this->layerFormatter->buildLayer( self::$bucketMap[self::CATEGORY_BUCKET]['label'], \count($categoryIds), self::$bucketMap[self::CATEGORY_BUCKET]['request_name'] @@ -116,7 +125,7 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array if (!\in_array($categoryId, $categoryIds, true)) { continue ; } - $result['filter_items'][] = $this->buildItem( + $result['options'][] = $this->layerFormatter->buildItem( $categoryLabels[$categoryId] ?? $categoryId, $categoryId, $value->getMetrics()['count'] @@ -126,40 +135,6 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array return [$result]; } - /** - * Format layer data - * - * @param string $layerName - * @param string $itemsCount - * @param string $requestName - * @return array - */ - private function buildLayer($layerName, $itemsCount, $requestName): array - { - return [ - 'name' => $layerName, - 'filter_items_count' => $itemsCount, - 'request_var' => $requestName - ]; - } - - /** - * Format layer item data - * - * @param string $label - * @param string|int $value - * @param string|int $count - * @return array - */ - private function buildItem($label, $value, $count): array - { - return [ - 'label' => $label, - 'value_string' => $value, - 'items_count' => $count, - ]; - } - /** * Check that bucket contains data * diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Formatter/LayerFormatter.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Formatter/LayerFormatter.php new file mode 100644 index 0000000000000..72495f8ef8524 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Formatter/LayerFormatter.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter; + +/** + * Format Layered Navigation Items + */ +class LayerFormatter +{ + /** + * Format layer data + * + * @param string $layerName + * @param string $itemsCount + * @param string $requestName + * @return array + */ + public function buildLayer($layerName, $itemsCount, $requestName): array + { + return [ + 'label' => $layerName, + 'count' => $itemsCount, + 'attribute_code' => $requestName + ]; + } + + /** + * Format layer item data + * + * @param string $label + * @param string|int $value + * @param string|int $count + * @return array + */ + public function buildItem($label, $value, $count): array + { + return [ + 'label' => $label, + 'value' => $value, + 'count' => $count, + ]; + } +} diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php index 77f44afb4f679..e84a8f1b11078 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php @@ -10,6 +10,7 @@ use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilderInterface; use Magento\Framework\Api\Search\AggregationInterface; use Magento\Framework\Api\Search\BucketInterface; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter\LayerFormatter; /** * @inheritdoc @@ -21,6 +22,11 @@ class Price implements LayerBuilderInterface */ private const PRICE_BUCKET = 'price_bucket'; + /** + * @var LayerFormatter + */ + private $layerFormatter; + /** * @var array */ @@ -31,6 +37,15 @@ class Price implements LayerBuilderInterface ], ]; + /** + * @param LayerFormatter $layerFormatter + */ + public function __construct( + LayerFormatter $layerFormatter + ) { + $this->layerFormatter = $layerFormatter; + } + /** * @inheritdoc * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -42,7 +57,7 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array return []; } - $result = $this->buildLayer( + $result = $this->layerFormatter->buildLayer( self::$bucketMap[self::PRICE_BUCKET]['label'], \count($bucket->getValues()), self::$bucketMap[self::PRICE_BUCKET]['request_name'] @@ -50,7 +65,7 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array foreach ($bucket->getValues() as $value) { $metrics = $value->getMetrics(); - $result['filter_items'][] = $this->buildItem( + $result['options'][] = $this->layerFormatter->buildItem( \str_replace('_', '-', $metrics['value']), $metrics['value'], $metrics['count'] @@ -60,40 +75,6 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array return [$result]; } - /** - * Format layer data - * - * @param string $layerName - * @param string $itemsCount - * @param string $requestName - * @return array - */ - private function buildLayer($layerName, $itemsCount, $requestName): array - { - return [ - 'name' => $layerName, - 'filter_items_count' => $itemsCount, - 'request_var' => $requestName - ]; - } - - /** - * Format layer item data - * - * @param string $label - * @param string|int $value - * @param string|int $count - * @return array - */ - private function buildItem($label, $value, $count): array - { - return [ - 'label' => $label, - 'value_string' => $value, - 'items_count' => $count, - ]; - } - /** * Check that bucket contains data * diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Aggregations.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Aggregations.php new file mode 100644 index 0000000000000..47a1d1f977f9b --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Aggregations.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilder; +use Magento\Store\Api\Data\StoreInterface; + +/** + * Layered navigation filters resolver, used for GraphQL request processing. + */ +class Aggregations implements ResolverInterface +{ + /** + * @var Layer\DataProvider\Filters + */ + private $filtersDataProvider; + + /** + * @var LayerBuilder + */ + private $layerBuilder; + + /** + * @param \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider + * @param LayerBuilder $layerBuilder + */ + public function __construct( + \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider, + LayerBuilder $layerBuilder + ) { + $this->filtersDataProvider = $filtersDataProvider; + $this->layerBuilder = $layerBuilder; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['layer_type']) || !isset($value['search_result'])) { + return null; + } + + $aggregations = $value['search_result']->getSearchAggregation(); + + if ($aggregations) { + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + $storeId = (int)$store->getId(); + return $this->layerBuilder->build($aggregations, $storeId); + } else { + return []; + } + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php index 6ef4e72627e82..0ec7e12e42d55 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/LayerFilters.php @@ -10,8 +10,6 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilder; -use Magento\Store\Api\Data\StoreInterface; /** * Layered navigation filters resolver, used for GraphQL request processing. @@ -23,21 +21,13 @@ class LayerFilters implements ResolverInterface */ private $filtersDataProvider; - /** - * @var LayerBuilder - */ - private $layerBuilder; - /** * @param \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider - * @param LayerBuilder $layerBuilder */ public function __construct( - \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider, - LayerBuilder $layerBuilder + \Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters $filtersDataProvider ) { $this->filtersDataProvider = $filtersDataProvider; - $this->layerBuilder = $layerBuilder; } /** @@ -50,19 +40,10 @@ public function resolve( array $value = null, array $args = null ) { - if (!isset($value['layer_type']) || !isset($value['search_result'])) { + if (!isset($value['layer_type'])) { return null; } - $aggregations = $value['search_result']->getSearchAggregation(); - - if ($aggregations) { - /** @var StoreInterface $store */ - $store = $context->getExtensionAttributes()->getStore(); - $storeId = (int)$store->getId(); - return $this->layerBuilder->build($aggregations, $storeId); - } else { - return []; - } + return $this->filtersDataProvider->getData($value['layer_type']); } } diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index aadd43fafbd7f..8900eb869d268 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -270,7 +270,8 @@ type Products @doc(description: "The Products object is the top-level object ret items: [ProductInterface] @doc(description: "An array of products that match the specified search criteria.") page_info: SearchResultPageInfo @doc(description: "An object that includes the page_info and currentPage values specified in the query.") total_count: Int @doc(description: "The number of products returned.") - filters: [LayerFilter] @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\LayerFilters") @doc(description: "Layered navigation filters array.") + filters: [LayerFilter] @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\LayerFilters") @doc(description: "Layered navigation filters array.") @deprecated(reason: "Use aggregations instead") + aggregations: [Aggregation] @doc(description: "Layered navigation aggregations.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Aggregations") sort_fields: SortFields @doc(description: "An object that includes the default sort field and all available sort fields.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\SortFields") } @@ -401,6 +402,19 @@ interface LayerFilterItemInterface @typeResolver(class: "Magento\\CatalogGraphQl items_count: Int @doc(description: "Count of items by filter.") } +type Aggregation { + count: Int @doc(description: "Count of filter items in filter group.") + label: String @doc(description: "Layered navigation filter name.") + attribute_code: String! @doc(description: "Attribute code of the filter item.") + options: [AggregationOption] @doc(description: "Array of aggregation options.") +} + +type AggregationOption { + count: Int @doc(description: "Count of items by filter.") + label: String! @doc(description: "Filter label.") + value: String! @doc(description: "Value for filter request variable to be used in query.") +} + type LayerFilterItem implements LayerFilterItemInterface { } From c4025da024cbdf7b2f9fa694caee9171d6e71bd1 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Tue, 27 Aug 2019 12:49:19 -0500 Subject: [PATCH 456/841] MC-19618: Update schema for layered navigation output - static fixes --- app/code/Magento/CatalogGraphQl/composer.json | 1 + .../Magento/GraphQl/Catalog/ProductSearchTest.php | 14 ++++++++------ ...ts_with_layered_navigation_custom_attribute.php | 3 ++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/composer.json b/app/code/Magento/CatalogGraphQl/composer.json index 13fcbe9a7d357..1582f29c25951 100644 --- a/app/code/Magento/CatalogGraphQl/composer.json +++ b/app/code/Magento/CatalogGraphQl/composer.json @@ -10,6 +10,7 @@ "magento/module-search": "*", "magento/module-store": "*", "magento/module-eav-graph-ql": "*", + "magento/module-catalog-search": "*", "magento/framework": "*" }, "suggest": { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 169aba7c15734..990e2e0f31ac3 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -116,7 +116,8 @@ public function testLayeredNavigationWithConfigurableChildrenOutOfStock() $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); $response = $this->graphQlQuery($query); - // 1 product is returned since only one child product with attribute option1 from 1st Configurable product is OOS + // 1 product is returned since only one child product with + // attribute option1 from 1st Configurable product is OOS $this->assertEquals(1, $response['products']['total_count']); // Custom attribute filter layer data @@ -269,8 +270,9 @@ public function testFilterProductsByMultiSelectCustomAttribute() /** @var AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); array_shift($options); - $optionValues = array(); - for ($i = 0; $i < count($options); $i++) { + $optionValues = []; + $count = count($options); + for ($i = 0; $i < $count; $i++) { $optionValues[] = $options[$i]->getValue(); } $query = <<<QUERY @@ -411,7 +413,7 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() $layers[$layerIndex][0]['request_var'], $response['products']['filters'][$layerIndex]['request_var'], 'request_var does not match' - ) ; + ); } // Validate the price filter layer data from the response @@ -490,7 +492,7 @@ public function testFilterByCategoryIdAndCustomAttribute() $this->assertEquals(2, $response['products']['total_count']); $actualCategoryFilterItems = $response['products']['filters'][1]['filter_items']; //Validate the number of categories/sub-categories that contain the products with the custom attribute - $this->assertCount(6,$actualCategoryFilterItems); + $this->assertCount(6, $actualCategoryFilterItems); $expectedCategoryFilterItems = [ @@ -527,7 +529,7 @@ public function testFilterByCategoryIdAndCustomAttribute() $categoryFilterItems[$index][0]['items_count'], $actualCategoryFilterItems[$index]['items_count'], 'Products count in the category is incorrect' - ) ; + ); } } /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php index 6cd2819c2d296..864a931359bdd 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -115,7 +115,8 @@ 'catalog_product', $attributeSet->getId(), $attributeSet->getDefaultGroupId(), - $attribute1->getId()); + $attribute1->getId() + ); } $eavConfig->clear(); From 590947451c8c45854a9b3958417de06f50520b3d Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk <odubovyk@magento.com> Date: Tue, 27 Aug 2019 13:27:55 -0500 Subject: [PATCH 457/841] MC-19271: No Error notification when Uploading Incorrect SKU for the second store while creating an order - fixed --- .../Catalog/Model/Indexer/Product/Flat/Action/RelationTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RelationTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RelationTest.php index 85bcd54767e36..72f1d330ee7d6 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RelationTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Indexer/Product/Flat/Action/RelationTest.php @@ -122,6 +122,7 @@ protected function tearDown() */ public function testExecute() : void { + $this->markTestSkipped('MC-19675'); try { $this->indexer->execute(); } catch (\Magento\Framework\Exception\LocalizedException $e) { From 9f5714841e063f1d54b41a1b5374059552277162 Mon Sep 17 00:00:00 2001 From: Krissy Hiserote <khiserote@magento.com> Date: Tue, 27 Aug 2019 13:56:47 -0500 Subject: [PATCH 458/841] MC-16650: Product Attribute Type Price Not Displaying - use a different request generator for price, fix tests, and code --- .../Catalog/Model/Layer/FilterList.php | 4 +- .../Test/Unit/Model/Layer/FilterListTest.php | 9 +- .../Model/Layer/Filter/Decimal.php | 27 ++---- .../Model/Search/RequestGenerator.php | 9 +- .../Model/Search/RequestGenerator/Price.php | 46 +++++++++++ .../Search/RequestGenerator/DecimalTest.php | 18 +--- .../Search/RequestGenerator/PriceTest.php | 82 +++++++++++++++++++ app/code/Magento/CatalogSearch/etc/di.xml | 1 + 8 files changed, 150 insertions(+), 46 deletions(-) create mode 100644 app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php create mode 100644 app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/PriceTest.php diff --git a/app/code/Magento/Catalog/Model/Layer/FilterList.php b/app/code/Magento/Catalog/Model/Layer/FilterList.php index 7f06c97d3e8d9..b8e9b8ad4aaa5 100644 --- a/app/code/Magento/Catalog/Model/Layer/FilterList.php +++ b/app/code/Magento/Catalog/Model/Layer/FilterList.php @@ -7,8 +7,6 @@ namespace Magento\Catalog\Model\Layer; -use Magento\Catalog\Model\Product\Attribute\Backend\Price; - /** * Layer navigation filters */ @@ -112,7 +110,7 @@ protected function getAttributeFilterClass(\Magento\Catalog\Model\ResourceModel\ { $filterClassName = $this->filterTypes[self::ATTRIBUTE_FILTER]; - if ($attribute->getBackendModel() === Price::class) { + if ($attribute->getFrontendInput() === 'price') { $filterClassName = $this->filterTypes[self::PRICE_FILTER]; } elseif ($attribute->getBackendType() === 'decimal') { $filterClassName = $this->filterTypes[self::DECIMAL_FILTER]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php index 2d3c764cb6907..6943bc27fd77c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php @@ -8,7 +8,6 @@ namespace Magento\Catalog\Test\Unit\Model\Layer; use \Magento\Catalog\Model\Layer\FilterList; -use Magento\Catalog\Model\Product\Attribute\Backend\Price; class FilterListTest extends \PHPUnit\Framework\TestCase { @@ -97,8 +96,8 @@ public function getFiltersDataProvider() { return [ [ - 'method' => 'getBackendModel', - 'value' => Price::class, + 'method' => 'getFrontendInput', + 'value' => 'price', 'expectedClass' => 'PriceFilterClass', ], [ @@ -107,8 +106,8 @@ public function getFiltersDataProvider() 'expectedClass' => 'DecimalFilterClass', ], [ - 'method' => 'getBackendModel', - 'value' => null, + 'method' => 'getFrontendInput', + 'value' => 'text', 'expectedClass' => 'AttributeFilterClass', ] ]; diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php index 4c83c3f7184f1..596dc1bfc561d 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php @@ -72,11 +72,17 @@ public function apply(\Magento\Framework\App\RequestInterface $request) list($from, $to) = explode('-', $filter); + // When the range is 10-20 we only need to get products that are in the 10-19.99 range. + $toValue = $to; + if (!empty($toValue) && $from !== $toValue) { + $toValue -= 0.001; + } + $this->getLayer() ->getProductCollection() ->addFieldToFilter( $this->getAttributeModel()->getAttributeCode(), - ['from' => $from, 'to' => $this->getToRangeValue($from, $to)] + ['from' => $from, 'to' => $toValue] ); $this->getLayer()->getState()->addFilter( @@ -149,23 +155,4 @@ protected function renderRangeLabel($fromPrice, $toPrice) return __('%1 - %2', $formattedFromPrice, $this->priceCurrency->format($toPrice)); } } - - /** - * Get the to range value - * - * When the range is 10-20 we only need to get products that are in the 10-19.99 range. - * 20 should be in the next range group. - * - * @param float|string $from - * @param float|string $to - * @return float|string - */ - private function getToRangeValue($from, $to) - { - if (!empty($to) && $from !== $to) { - $to -= 0.001; - } - - return $to; - } } diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php index 0adc2fcecbfa7..82264c5d4cc23 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php +++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\CatalogSearch\Model\Search; use Magento\Catalog\Api\Data\EavAttributeInterface; @@ -78,6 +80,7 @@ private function generateRequest($attributeType, $container, $useFulltext) { $request = []; foreach ($this->getSearchableAttributes() as $attribute) { + /** @var $attribute Attribute */ if ($attribute->getData($attributeType)) { if (!in_array($attribute->getAttributeCode(), ['price', 'category_ids'], true)) { $queryName = $attribute->getAttributeCode() . '_query'; @@ -97,12 +100,14 @@ private function generateRequest($attributeType, $container, $useFulltext) ], ]; $bucketName = $attribute->getAttributeCode() . self::BUCKET_SUFFIX; - $generator = $this->generatorResolver->getGeneratorForType($attribute->getBackendType()); + $generatorType = $attribute->getFrontendInput() === 'price' + ? $attribute->getFrontendInput() + : $attribute->getBackendType(); + $generator = $this->generatorResolver->getGeneratorForType($generatorType); $request['filters'][$filterName] = $generator->getFilterData($attribute, $filterName); $request['aggregations'][$bucketName] = $generator->getAggregationData($attribute, $bucketName); } } - /** @var $attribute Attribute */ if (!$attribute->getIsSearchable() || in_array($attribute->getAttributeCode(), ['price', 'sku'], true)) { // Some fields have their own specific handlers continue; diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php new file mode 100644 index 0000000000000..7bf124844c618 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Model\Search\RequestGenerator; + +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Framework\Search\Request\BucketInterface; +use Magento\Framework\Search\Request\FilterInterface; + +/** + * Catalog search range request generator. + */ +class Price implements GeneratorInterface +{ + /** + * @inheritdoc + */ + public function getFilterData(Attribute $attribute, $filterName) + { + return [ + 'type' => FilterInterface::TYPE_RANGE, + 'name' => $filterName, + 'field' => $attribute->getAttributeCode(), + 'from' => '$' . $attribute->getAttributeCode() . '.from$', + 'to' => '$' . $attribute->getAttributeCode() . '.to$', + ]; + } + + /** + * @inheritdoc + */ + public function getAggregationData(Attribute $attribute, $bucketName) + { + return [ + 'type' => BucketInterface::TYPE_DYNAMIC, + 'name' => $bucketName, + 'field' => $attribute->getAttributeCode(), + 'method' => '$price_dynamic_algorithm$', + 'metric' => [['type' => 'count']], + ]; + } +} diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php index f04188fbf7bdd..350344372612a 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/DecimalTest.php @@ -11,7 +11,6 @@ use Magento\CatalogSearch\Model\Search\RequestGenerator\Decimal; use Magento\Framework\Search\Request\BucketInterface; use Magento\Framework\Search\Request\FilterInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; /** * Test catalog search range request generator. @@ -24,23 +23,14 @@ class DecimalTest extends \PHPUnit\Framework\TestCase /** @var Attribute|\PHPUnit_Framework_MockObject_MockObject */ private $attribute; - /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $scopeConfigMock; - protected function setUp() { $this->attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->setMethods(['getAttributeCode']) ->getMockForAbstractClass(); - $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) - ->setMethods(['getValue']) - ->getMockForAbstractClass(); $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->decimal = $objectManager->getObject( - Decimal::class, - ['scopeConfig' => $this->scopeConfigMock] - ); + $this->decimal = $objectManager->getObject(Decimal::class); } public function testGetFilterData() @@ -65,20 +55,16 @@ public function testGetAggregationData() { $bucketName = 'test_bucket_name'; $attributeCode = 'test_attribute_code'; - $method = 'manual'; $expected = [ 'type' => BucketInterface::TYPE_DYNAMIC, 'name' => $bucketName, 'field' => $attributeCode, - 'method' => $method, + 'method' => 'manual', 'metric' => [['type' => 'count']], ]; $this->attribute->expects($this->atLeastOnce()) ->method('getAttributeCode') ->willReturn($attributeCode); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->willReturn($method); $actual = $this->decimal->getAggregationData($this->attribute, $bucketName); $this->assertEquals($expected, $actual); } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/PriceTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/PriceTest.php new file mode 100644 index 0000000000000..3635430197591 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/RequestGenerator/PriceTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogSearch\Test\Unit\Model\Search\RequestGenerator; + +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\CatalogSearch\Model\Search\RequestGenerator\Price; +use Magento\Framework\Search\Request\BucketInterface; +use Magento\Framework\Search\Request\FilterInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Test catalog search range request generator. + */ +class PriceTest extends \PHPUnit\Framework\TestCase +{ + /** @var Price */ + private $price; + + /** @var Attribute|\PHPUnit_Framework_MockObject_MockObject */ + private $attribute; + + /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ + private $scopeConfigMock; + + protected function setUp() + { + $this->attribute = $this->getMockBuilder(Attribute::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributeCode']) + ->getMockForAbstractClass(); + $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->setMethods(['getValue']) + ->getMockForAbstractClass(); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->price = $objectManager->getObject( + Price::class, + ['scopeConfig' => $this->scopeConfigMock] + ); + } + + public function testGetFilterData() + { + $filterName = 'test_filter_name'; + $attributeCode = 'test_attribute_code'; + $expected = [ + 'type' => FilterInterface::TYPE_RANGE, + 'name' => $filterName, + 'field' => $attributeCode, + 'from' => '$' . $attributeCode . '.from$', + 'to' => '$' . $attributeCode . '.to$', + ]; + $this->attribute->expects($this->atLeastOnce()) + ->method('getAttributeCode') + ->willReturn($attributeCode); + $actual = $this->price->getFilterData($this->attribute, $filterName); + $this->assertEquals($expected, $actual); + } + + public function testGetAggregationData() + { + $bucketName = 'test_bucket_name'; + $attributeCode = 'test_attribute_code'; + $method = 'price_dynamic_algorithm'; + $expected = [ + 'type' => BucketInterface::TYPE_DYNAMIC, + 'name' => $bucketName, + 'field' => $attributeCode, + 'method' => '$'. $method . '$', + 'metric' => [['type' => 'count']], + ]; + $this->attribute->expects($this->atLeastOnce()) + ->method('getAttributeCode') + ->willReturn($attributeCode); + $actual = $this->price->getAggregationData($this->attribute, $bucketName); + $this->assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml index 7359bd6b454b9..6c9010f306e14 100644 --- a/app/code/Magento/CatalogSearch/etc/di.xml +++ b/app/code/Magento/CatalogSearch/etc/di.xml @@ -279,6 +279,7 @@ <argument name="defaultGenerator" xsi:type="object">\Magento\CatalogSearch\Model\Search\RequestGenerator\General</argument> <argument name="generators" xsi:type="array"> <item name="decimal" xsi:type="object">Magento\CatalogSearch\Model\Search\RequestGenerator\Decimal</item> + <item name="price" xsi:type="object">Magento\CatalogSearch\Model\Search\RequestGenerator\Price</item> </argument> </arguments> </type> From f6c67702052188bcdc1ea018cff9a64a12b91f27 Mon Sep 17 00:00:00 2001 From: Dmytro Poperechnyy <dpoperechnyy@magento.com> Date: Tue, 27 Aug 2019 14:10:41 -0500 Subject: [PATCH 459/841] MC-19246: Fix missing shim configurations --- app/code/Magento/Theme/view/base/requirejs-config.js | 3 ++- app/code/Magento/Theme/view/frontend/requirejs-config.js | 2 +- lib/web/jquery/patches/jquery-ui.js | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Theme/view/base/requirejs-config.js b/app/code/Magento/Theme/view/base/requirejs-config.js index 2822d6db008a1..4da4615333d0b 100644 --- a/app/code/Magento/Theme/view/base/requirejs-config.js +++ b/app/code/Magento/Theme/view/base/requirejs-config.js @@ -32,7 +32,8 @@ var config = { }, 'jquery/jquery-storageapi': { 'deps': ['jquery/jquery.cookie'] - } + }, + 'magnifier/magnifier': ['jquery'] }, 'paths': { 'jquery/validate': 'jquery/jquery.validate', diff --git a/app/code/Magento/Theme/view/frontend/requirejs-config.js b/app/code/Magento/Theme/view/frontend/requirejs-config.js index d1cf76b83ebb4..c41a0602ef3e8 100644 --- a/app/code/Magento/Theme/view/frontend/requirejs-config.js +++ b/app/code/Magento/Theme/view/frontend/requirejs-config.js @@ -44,7 +44,7 @@ var config = { 'Magento_Theme/js/view/breadcrumbs': { 'Magento_Theme/js/view/add-home-breadcrumb': true }, - 'jquery/jquery-ui': { + 'jquery/ui-modules/dialog': { 'jquery/patches/jquery-ui': true } } diff --git a/lib/web/jquery/patches/jquery-ui.js b/lib/web/jquery/patches/jquery-ui.js index ae2d8da7ece66..64465430cc584 100644 --- a/lib/web/jquery/patches/jquery-ui.js +++ b/lib/web/jquery/patches/jquery-ui.js @@ -4,7 +4,9 @@ */ define([ - 'jquery' + 'jquery', + 'jquery-ui-modules/widget', + 'jquery-ui-modules/dialog' ], function ($) { 'use strict'; From 39ca7234706d32a69eb01e175e4574cc0cb2b504 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Tue, 27 Aug 2019 14:19:49 -0500 Subject: [PATCH 460/841] magento/graphql-ce#816: Root category ID --- .../Model/Category/GetRootCategoryId.php | 56 ------------------- .../Model/Resolver/CategoryRoot.php | 49 ---------------- .../Model/Resolver/CategoryTree.php | 37 ++---------- .../Model/Resolver/RootCategoryId.php | 26 +++++++++ .../CatalogGraphQl/etc/schema.graphqls | 2 +- 5 files changed, 32 insertions(+), 138 deletions(-) delete mode 100644 app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php delete mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php deleted file mode 100644 index 4da1443c9e05f..0000000000000 --- a/app/code/Magento/CatalogGraphQl/Model/Category/GetRootCategoryId.php +++ /dev/null @@ -1,56 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\CatalogGraphQl\Model\Category; - -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Store\Model\StoreManagerInterface; - -/** - * Gets Root Category for Current Store. - */ -class GetRootCategoryId -{ - /** - * @var int - */ - private $rootCategoryId; - /** - * @var StoreManagerInterface - */ - private $storeManager; - - /** - * GetRootCategoryId constructor. - * @param StoreManagerInterface $storeManager - */ - public function __construct( - StoreManagerInterface $storeManager - ) { - $this->storeManager = $storeManager; - } - - /** - * Get Root Category Id - * - * @return int - * @throws LocalizedException - */ - public function execute() - { - if ($this->rootCategoryId == null) { - try { - $this->rootCategoryId = (int)$this->storeManager->getStore()->getRootCategoryId(); - } catch (NoSuchEntityException $noSuchEntityException) { - throw new LocalizedException(__("Store does not exist.")); - } - } - - return $this->rootCategoryId; - } -} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php deleted file mode 100644 index 9720a6d6244d5..0000000000000 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryRoot.php +++ /dev/null @@ -1,49 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\CatalogGraphQl\Model\Resolver; - -use Magento\CatalogGraphQl\Model\Category\GetRootCategoryId; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\GraphQl\Config\Element\Field; -use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; -use Magento\Framework\GraphQl\Query\ResolverInterface; -use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; - -/** - * Category tree field resolver, used for GraphQL request processing. - */ -class CategoryRoot implements ResolverInterface -{ - /** - * @var GetRootCategoryId - */ - private $getRootCategoryId; - - /** - * @param GetRootCategoryId $getRootCategoryId - */ - public function __construct( - GetRootCategoryId $getRootCategoryId - ) { - $this->getRootCategoryId = $getRootCategoryId; - } - - /** - * @inheritdoc - */ - public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) - { - $rootCategoryId = 0; - try { - $rootCategoryId = $this->getRootCategoryId->execute(); - } catch (LocalizedException $exception) { - throw new GraphQlNoSuchEntityException(__('Store doesn\'t exist')); - } - return $rootCategoryId; - } -} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index f99e80eea493c..f72fb317ef3dc 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -7,10 +7,8 @@ namespace Magento\CatalogGraphQl\Model\Resolver; -use Magento\Catalog\Model\Category; use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; @@ -40,45 +38,20 @@ class CategoryTree implements ResolverInterface * @var CheckCategoryIsActive */ private $checkCategoryIsActive; - /** - * @var \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId - */ - private $getRootCategoryId; /** * @param Products\DataProvider\CategoryTree $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CheckCategoryIsActive $checkCategoryIsActive - * @param \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId $getRootCategoryId */ public function __construct( Products\DataProvider\CategoryTree $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, - CheckCategoryIsActive $checkCategoryIsActive, - \Magento\CatalogGraphQl\Model\Category\GetRootCategoryId $getRootCategoryId + CheckCategoryIsActive $checkCategoryIsActive ) { $this->categoryTree = $categoryTree; $this->extractDataFromCategoryTree = $extractDataFromCategoryTree; $this->checkCategoryIsActive = $checkCategoryIsActive; - $this->getRootCategoryId = $getRootCategoryId; - } - - /** - * Get category id - * - * @param array $args - * @return int - * @throws GraphQlNoSuchEntityException - */ - private function getCategoryId(array $args) : int - { - $rootCategoryId = 0; - try { - $rootCategoryId = isset($args['id']) ? (int)$args['id'] : $this->getRootCategoryId->execute(); - } catch (LocalizedException $exception) { - throw new GraphQlNoSuchEntityException(__('Store doesn\'t exist')); - } - return $rootCategoryId; } /** @@ -90,10 +63,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value return $value[$field->getName()]; } - $rootCategoryId = $this->getCategoryId($args); - if ($rootCategoryId !== $this->getRootCategoryId->execute() && $rootCategoryId > 0) { - $this->checkCategoryIsActive->execute($rootCategoryId); - } + $rootCategoryId = isset($args['id']) ? (int)$args['id'] : + $context->getExtensionAttributes()->getStore()->getRootCategoryId(); + $this->checkCategoryIsActive->execute($rootCategoryId); + $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); if (empty($categoriesTree) || ($categoriesTree->count() == 0)) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php new file mode 100644 index 0000000000000..add47e3107ec9 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Root category tree field resolver, used for GraphQL request processing. + */ +class RootCategoryId implements ResolverInterface +{ + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + return $context->getExtensionAttributes()->getStore()->getRootCategoryId(); + } +} diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 554ad4b45cf99..d8a3b5e744d17 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -416,7 +416,7 @@ type StoreConfig @doc(description: "The type contains information about a store grid_per_page : Int @doc(description: "Products per Page on Grid Default Value.") list_per_page : Int @doc(description: "Products per Page on List Default Value.") catalog_default_sort_by : String @doc(description: "Default Sort By.") - root_category_id: Int @doc(description: "The ID ot root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryRoot") + root_category_id: Int @doc(description: "The ID of the root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\RootCategoryId") } type ProductVideo @doc(description: "Contains information about a product video.") implements MediaGalleryInterface { From 8f491d3dcc905b12b08b5327999ce51e658a34ac Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Tue, 27 Aug 2019 14:27:21 -0500 Subject: [PATCH 461/841] building errors --- app/code/Magento/Wishlist/Model/Wishlist.php | 11 +- .../Wishlist/Test/Unit/Model/WishlistTest.php | 173 ++++++++++++------ 2 files changed, 122 insertions(+), 62 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/Wishlist.php b/app/code/Magento/Wishlist/Model/Wishlist.php index bf43fbd6f4363..0d9c74edb3d4c 100644 --- a/app/code/Magento/Wishlist/Model/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Wishlist.php @@ -176,6 +176,7 @@ class Wishlist extends AbstractModel implements IdentityInterface * @param DateTime $dateTime * @param ProductRepositoryInterface $productRepository * @param StockItemRepository $stockItemRepository + * @param ScopeConfigInterface|null $scopeConfig * @param bool $useCurrentWebsite * @param array $data * @param Json|null $serializer @@ -400,7 +401,7 @@ public function getItem($itemId) * Adding item to wishlist * * @param Item $item - * @return $this + * @return $this * @throws Exception */ public function addItem(Item $item) @@ -424,7 +425,8 @@ public function addItem(Item $item) * @return Item|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) - *@throws LocalizedException + * @throws LocalizedException + * @throws InvalidArgumentException */ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false) { @@ -452,7 +454,8 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false throw new LocalizedException(__('Cannot specify product.')); } - $stockItem = $this->stockItemRepository->get($product->getId()); + /** @var \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem */ + $stockItem = $this->stockItemRepository->get($productId); $showOutOfStock = $this->scopeConfig->isSetFlag( Configuration::XML_PATH_SHOW_OUT_OF_STOCK, ScopeInterface::SCOPE_STORE @@ -470,7 +473,7 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false if (!is_array($buyRequestData)) { $isInvalidItemConfiguration = true; } - } catch (InvalidArgumentException $exception) { + } catch (Exception $exception) { $isInvalidItemConfiguration = true; } if ($isInvalidItemConfiguration) { diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php index ff8a3a3b87cec..cbbccf29e63b8 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php @@ -5,76 +5,103 @@ */ namespace Magento\Wishlist\Test\Unit\Model; +use ArrayIterator; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Helper\Product as HelperProduct; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Type\AbstractType; +use Magento\Catalog\Model\ProductFactory; +use Magento\CatalogInventory\Model\Stock\Item as StockItem; +use Magento\CatalogInventory\Model\Stock\StockItemRepository; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\DataObject; +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Math\Random; +use Magento\Framework\Model\Context; +use Magento\Framework\Registry; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\DateTime; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Wishlist\Helper\Data; +use Magento\Wishlist\Model\Item; +use Magento\Wishlist\Model\ItemFactory; +use Magento\Wishlist\Model\ResourceModel\Item\Collection; +use Magento\Wishlist\Model\ResourceModel\Item\CollectionFactory; +use Magento\Wishlist\Model\ResourceModel\Wishlist as WishlistResource; +use Magento\Wishlist\Model\ResourceModel\Wishlist\Collection as WishlistCollection; use Magento\Wishlist\Model\Wishlist; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) */ -class WishlistTest extends \PHPUnit\Framework\TestCase +class WishlistTest extends TestCase { /** - * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject + * @var Registry|PHPUnit_Framework_MockObject_MockObject */ protected $registry; /** - * @var \Magento\Catalog\Helper\Product|\PHPUnit_Framework_MockObject_MockObject + * @var HelperProduct|PHPUnit_Framework_MockObject_MockObject */ protected $productHelper; /** - * @var \Magento\Wishlist\Helper\Data|\PHPUnit_Framework_MockObject_MockObject + * @var Data|PHPUnit_Framework_MockObject_MockObject */ protected $helper; /** - * @var \Magento\Wishlist\Model\ResourceModel\Wishlist|\PHPUnit_Framework_MockObject_MockObject + * @var WishlistResource|PHPUnit_Framework_MockObject_MockObject */ protected $resource; /** - * @var \Magento\Wishlist\Model\ResourceModel\Wishlist\Collection|\PHPUnit_Framework_MockObject_MockObject + * @var WishlistCollection|PHPUnit_Framework_MockObject_MockObject */ protected $collection; /** - * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|PHPUnit_Framework_MockObject_MockObject */ protected $storeManager; /** - * @var \Magento\Framework\Stdlib\DateTime\DateTime|\PHPUnit_Framework_MockObject_MockObject + * @var DateTime\DateTime|PHPUnit_Framework_MockObject_MockObject */ protected $date; /** - * @var \Magento\Wishlist\Model\ItemFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ItemFactory|PHPUnit_Framework_MockObject_MockObject */ protected $itemFactory; /** - * @var \Magento\Wishlist\Model\ResourceModel\Item\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + * @var CollectionFactory|PHPUnit_Framework_MockObject_MockObject */ protected $itemsFactory; /** - * @var \Magento\Catalog\Model\ProductFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ProductFactory|PHPUnit_Framework_MockObject_MockObject */ protected $productFactory; /** - * @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject + * @var Random|PHPUnit_Framework_MockObject_MockObject */ protected $mathRandom; /** - * @var \Magento\Framework\Stdlib\DateTime|\PHPUnit_Framework_MockObject_MockObject + * @var DateTime|PHPUnit_Framework_MockObject_MockObject */ protected $dateTime; /** - * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ManagerInterface|PHPUnit_Framework_MockObject_MockObject */ protected $eventDispatcher; @@ -84,63 +111,79 @@ class WishlistTest extends \PHPUnit\Framework\TestCase protected $wishlist; /** - * @var \Magento\Catalog\Api\ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ProductRepositoryInterface|PHPUnit_Framework_MockObject_MockObject */ protected $productRepository; /** - * @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject + * @var Json|PHPUnit_Framework_MockObject_MockObject */ protected $serializer; + /** + * @var StockItemRepository|PHPUnit_Framework_MockObject_MockObject + */ + private $stockItemRepository; + + /** + * @var StockItemRepository|PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfig; + protected function setUp() { - $context = $this->getMockBuilder(\Magento\Framework\Model\Context::class) + $context = $this->getMockBuilder(Context::class) ->disableOriginalConstructor() ->getMock(); - $this->eventDispatcher = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) + $this->eventDispatcher = $this->getMockBuilder(ManagerInterface::class) ->getMock(); - $this->registry = $this->getMockBuilder(\Magento\Framework\Registry::class) + $this->registry = $this->getMockBuilder(Registry::class) ->disableOriginalConstructor() ->getMock(); - $this->productHelper = $this->getMockBuilder(\Magento\Catalog\Helper\Product::class) + $this->productHelper = $this->getMockBuilder(HelperProduct::class) ->disableOriginalConstructor() ->getMock(); - $this->helper = $this->getMockBuilder(\Magento\Wishlist\Helper\Data::class) + $this->helper = $this->getMockBuilder(Data::class) ->disableOriginalConstructor() ->getMock(); - $this->resource = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Wishlist::class) + $this->resource = $this->getMockBuilder(WishlistResource::class) ->disableOriginalConstructor() ->getMock(); - $this->collection = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Wishlist\Collection::class) + $this->collection = $this->getMockBuilder(WishlistCollection::class) ->disableOriginalConstructor() ->getMock(); - $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) ->getMock(); - $this->date = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\DateTime::class) + $this->date = $this->getMockBuilder(DateTime\DateTime::class) ->disableOriginalConstructor() ->getMock(); - $this->itemFactory = $this->getMockBuilder(\Magento\Wishlist\Model\ItemFactory::class) + $this->itemFactory = $this->getMockBuilder(ItemFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->itemsFactory = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Item\CollectionFactory::class) + $this->itemsFactory = $this->getMockBuilder(CollectionFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->productFactory = $this->getMockBuilder(\Magento\Catalog\Model\ProductFactory::class) + $this->productFactory = $this->getMockBuilder(ProductFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->mathRandom = $this->getMockBuilder(\Magento\Framework\Math\Random::class) + $this->mathRandom = $this->getMockBuilder(Random::class) ->disableOriginalConstructor() ->getMock(); - $this->dateTime = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime::class) + $this->dateTime = $this->getMockBuilder(DateTime::class) ->disableOriginalConstructor() ->getMock(); - $this->productRepository = $this->createMock(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $this->serializer = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class) + $this->productRepository = $this->createMock(ProductRepositoryInterface::class); + $this->stockItemRepository = $this->createMock(StockItemRepository::class); + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); + + $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->serializer = $this->getMockBuilder(Json::class) ->disableOriginalConstructor() ->getMock(); @@ -163,6 +206,8 @@ protected function setUp() $this->mathRandom, $this->dateTime, $this->productRepository, + $this->stockItemRepository, + $this->scopeConfig, false, [], $this->serializer @@ -186,7 +231,7 @@ public function testLoadByCustomerId() ->will($this->returnValue($sharingCode)); $this->assertInstanceOf( - \Magento\Wishlist\Model\Wishlist::class, + Wishlist::class, $this->wishlist->loadByCustomerId($customerId, true) ); $this->assertEquals($customerId, $this->wishlist->getCustomerId()); @@ -194,10 +239,10 @@ public function testLoadByCustomerId() } /** - * @param int|\Magento\Wishlist\Model\Item|\PHPUnit_Framework_MockObject_MockObject $itemId - * @param \Magento\Framework\DataObject $buyRequest - * @param null|array|\Magento\Framework\DataObject $param - * @throws \Magento\Framework\Exception\LocalizedException + * @param int|Item|PHPUnit_Framework_MockObject_MockObject $itemId + * @param DataObject $buyRequest + * @param null|array|DataObject $param + * @throws LocalizedException * * @dataProvider updateItemDataProvider */ @@ -205,9 +250,9 @@ public function testUpdateItem($itemId, $buyRequest, $param) { $storeId = 1; $productId = 1; - $stores = [(new \Magento\Framework\DataObject())->setId($storeId)]; + $stores = [(new DataObject())->setId($storeId)]; - $newItem = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + $newItem = $this->getMockBuilder(Item::class) ->setMethods( ['setProductId', 'setWishlistId', 'setStoreId', 'setOptions', 'setProduct', 'setQty', 'getItem', 'save'] ) @@ -228,12 +273,16 @@ public function testUpdateItem($itemId, $buyRequest, $param) $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($stores[0])); $product = $this->getMockBuilder( - \Magento\Catalog\Model\Product::class + Product::class )->disableOriginalConstructor()->getMock(); $product->expects($this->any())->method('getId')->will($this->returnValue($productId)); $product->expects($this->any())->method('getStoreId')->will($this->returnValue($storeId)); - $instanceType = $this->getMockBuilder(\Magento\Catalog\Model\Product\Type\AbstractType::class) + $stockItem = $this->getMockBuilder(StockItem::class)->disableOriginalConstructor()->getMock(); + $stockItem->expects($this->any())->method('getIsInStock')->will($this->returnValue(true)); + $this->stockItemRepository->expects($this->any())->method('get')->will($this->returnValue($stockItem)); + + $instanceType = $this->getMockBuilder(AbstractType::class) ->disableOriginalConstructor() ->getMock(); $instanceType->expects($this->once()) @@ -241,13 +290,13 @@ public function testUpdateItem($itemId, $buyRequest, $param) ->will( $this->returnValue( $this->getMockBuilder( - \Magento\Catalog\Model\Product::class + Product::class )->disableOriginalConstructor()->getMock() ) ); $newProduct = $this->getMockBuilder( - \Magento\Catalog\Model\Product::class + Product::class )->disableOriginalConstructor()->getMock(); $newProduct->expects($this->any()) ->method('setStoreId') @@ -257,12 +306,12 @@ public function testUpdateItem($itemId, $buyRequest, $param) ->method('getTypeInstance') ->will($this->returnValue($instanceType)); - $item = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class)->disableOriginalConstructor()->getMock(); + $item = $this->getMockBuilder(Item::class)->disableOriginalConstructor()->getMock(); $item->expects($this->once()) ->method('getProduct') ->will($this->returnValue($product)); - $items = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Item\Collection::class) + $items = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); @@ -280,7 +329,7 @@ public function testUpdateItem($itemId, $buyRequest, $param) ->will($this->returnValue($item)); $items->expects($this->any()) ->method('getIterator') - ->will($this->returnValue(new \ArrayIterator([$item]))); + ->will($this->returnValue(new ArrayIterator([$item]))); $this->itemsFactory->expects($this->any()) ->method('create') @@ -292,7 +341,7 @@ public function testUpdateItem($itemId, $buyRequest, $param) ->will($this->returnValue($newProduct)); $this->assertInstanceOf( - \Magento\Wishlist\Model\Wishlist::class, + Wishlist::class, $this->wishlist->updateItem($itemId, $buyRequest, $param) ); } @@ -303,7 +352,7 @@ public function testUpdateItem($itemId, $buyRequest, $param) public function updateItemDataProvider() { return [ - '0' => [1, new \Magento\Framework\DataObject(), null] + '0' => [1, new DataObject(), null] ]; } @@ -311,24 +360,26 @@ public function testAddNewItem() { $productId = 1; $storeId = 1; - $buyRequest = json_encode([ - 'number' => 42, - 'string' => 'string_value', - 'boolean' => true, - 'collection' => [1, 2, 3], - 'product' => 1, - 'form_key' => 'abc' - ]); + $buyRequest = json_encode( + [ + 'number' => 42, + 'string' => 'string_value', + 'boolean' => true, + 'collection' => [1, 2, 3], + 'product' => 1, + 'form_key' => 'abc' + ] + ); $result = 'product'; - $instanceType = $this->getMockBuilder(\Magento\Catalog\Model\Product\Type\AbstractType::class) + $instanceType = $this->getMockBuilder(AbstractType::class) ->disableOriginalConstructor() ->getMock(); $instanceType->expects($this->once()) ->method('processConfiguration') ->willReturn('product'); - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $productMock = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->setMethods(['getId', 'hasWishlistStoreId', 'getStoreId', 'getTypeInstance']) ->getMock(); @@ -358,6 +409,12 @@ function ($value) { } ); + $stockItem = $this->getMockBuilder( + StockItem::class + )->disableOriginalConstructor()->getMock(); + $stockItem->expects($this->any())->method('getIsInStock')->will($this->returnValue(true)); + $this->stockItemRepository->expects($this->any())->method('get')->will($this->returnValue($stockItem)); + $this->assertEquals($result, $this->wishlist->addNewItem($productMock, $buyRequest)); } } From fc5a7ff90b1881ebde1b98e95efa2d3c31ca3645 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Tue, 27 Aug 2019 16:17:47 -0400 Subject: [PATCH 462/841] Change cart total_quanity to float and make optional --- .../Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php | 2 +- app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php index 320a103c62108..014b7cf90f3eb 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php @@ -29,6 +29,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value /** @var Quote $cart */ $cart = $value['model']; - return $cart->getItemsSummaryQty(); + return (float)$cart->getItemsSummaryQty(); } } diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index b033d6d06a899..becdd21462f7a 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -197,7 +197,7 @@ type Cart { available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods") selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod") prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices") - total_quantity: Int! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTotalQuantity") + total_quantity: Float @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTotalQuantity") } interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") { From 96da3e34504de6f5ba16c7ab0ec0a8932a7aed51 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Tue, 27 Aug 2019 15:53:08 -0500 Subject: [PATCH 463/841] getRootCategoryId returns a string, and getTree expected an int --- .../Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index f72fb317ef3dc..00d6c00cd32c3 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -13,6 +13,7 @@ use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\GraphQl\Model\Query\ContextInterface; /** * Category tree field resolver, used for GraphQL request processing. @@ -64,7 +65,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } $rootCategoryId = isset($args['id']) ? (int)$args['id'] : - $context->getExtensionAttributes()->getStore()->getRootCategoryId(); + (int)$context->getExtensionAttributes()->getStore()->getRootCategoryId(); $this->checkCategoryIsActive->execute($rootCategoryId); $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); From 6d4a56825758397a61fc64b1c7756d4994cdd7f1 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Tue, 27 Aug 2019 16:06:07 -0500 Subject: [PATCH 464/841] MC-19296: PayPal Express Guest Checkout Issue --- .../Paypal/Test/Unit/Model/SmartButtonConfigTest.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php index 6298df3ee8e23..5aa3dee0874b2 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php @@ -104,7 +104,11 @@ public function testGetConfig( ["{$page}_page_button_color", null, $color], ["{$page}_page_button_shape", null, $shape], ["{$page}_page_button_label", null, $label], - [$page . '_page_button_' . $installmentPeriodLocale . '_installment_period', null, $installmentPeriodLabel] + [ + $page . '_page_button_' . $installmentPeriodLocale . '_installment_period', + null, + $installmentPeriodLabel + ] ] ) ); From 5731e7d587c5275a2cae1f2acec09c969a4570a3 Mon Sep 17 00:00:00 2001 From: Jason Sylvester <helseendjels@hetnet.nl> Date: Tue, 27 Aug 2019 23:12:04 +0200 Subject: [PATCH 465/841] Change Regex check on classtype #24323 --- lib/internal/Magento/Framework/Indexer/etc/indexer.xsd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd b/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd index 6a06a4d55ee35..fc38a5206e855 100644 --- a/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd +++ b/lib/internal/Magento/Framework/Indexer/etc/indexer.xsd @@ -67,11 +67,11 @@ <xs:simpleType name="classType"> <xs:annotation> <xs:documentation> - Class name can contain only [a-zA-Z\]. + Class name can contain only [a-zA-Z|\\]+[a-zA-Z0-9\\]+. </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[a-zA-Z\\]+" /> + <xs:pattern value="[a-zA-Z|\\]+[a-zA-Z0-9\\]+" /> </xs:restriction> </xs:simpleType> From 8d742bd82264960a243e2513df59ec77c97883b2 Mon Sep 17 00:00:00 2001 From: Jason Sylvester <helseendjels@hetnet.nl> Date: Tue, 27 Aug 2019 23:18:01 +0200 Subject: [PATCH 466/841] Change Regex check on classtype #24323 --- lib/internal/Magento/Framework/Mview/etc/mview.xsd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/Mview/etc/mview.xsd b/lib/internal/Magento/Framework/Mview/etc/mview.xsd index e2f185041962a..dfff4964f6587 100644 --- a/lib/internal/Magento/Framework/Mview/etc/mview.xsd +++ b/lib/internal/Magento/Framework/Mview/etc/mview.xsd @@ -51,11 +51,11 @@ <xs:simpleType name="classType"> <xs:annotation> <xs:documentation> - Class name can contain only [a-zA-Z\]. + Class name can contain only [a-zA-Z|\\]+[a-zA-Z0-9\\]+. </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[a-zA-Z\\]+" /> + <xs:pattern value="[a-zA-Z|\\]+[a-zA-Z0-9\\]+" /> </xs:restriction> </xs:simpleType> From eae167e7dc6ef0e75d00797a3461cb7befdacc5d Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Tue, 27 Aug 2019 16:36:12 -0500 Subject: [PATCH 467/841] magento/graphql-ce#827: Cart Item Quantity --- app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index becdd21462f7a..c458b5e9dc05d 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -197,7 +197,7 @@ type Cart { available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods") selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod") prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices") - total_quantity: Float @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTotalQuantity") + total_quantity: Float! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTotalQuantity") } interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") { From bd5ac9752c2fc6c9ed17332d5688f8820ceada16 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Tue, 27 Aug 2019 16:42:54 -0500 Subject: [PATCH 468/841] magento/graphql-ce#685: Extract logic related to customer from DownloadableGraphQl module into new one --- app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml | 3 --- app/code/Magento/DownloadableGraphQl/etc/schema.graphqls | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml index 6dc201eb00ef9..934bc81ae629a 100644 --- a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml +++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml @@ -8,10 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> <module name="Magento_CustomerDownloadableGraphQl" > <sequence> - <module name="Magento_Catalog"/> - <module name="Magento_Downloadable"/> <module name="Magento_GraphQl"/> - <module name="Magento_CatalogGraphQl"/> <module name="Magento_DownloadableGraphQl"/> </sequence> </module> diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index 245e2308488ac..57c3b822d6797 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -62,4 +62,4 @@ type DownloadableProductSamples @doc(description: "DownloadableProductSamples de sample_url: String @doc(description: "URL to the downloadable sample") sample_type: DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample") sample_file: String @deprecated(reason: "`sample_url` serves to get the downloadable sample") -} \ No newline at end of file +} From fe6dd4bb4b471cffc5cc3a93b2ea70baab5428db Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Tue, 27 Aug 2019 16:52:17 -0500 Subject: [PATCH 469/841] magento/graphql-ce#816: Root category ID --- .../Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 00d6c00cd32c3..b726615b2f4d7 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -13,7 +13,6 @@ use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\GraphQl\Model\Query\ContextInterface; /** * Category tree field resolver, used for GraphQL request processing. @@ -64,7 +63,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value return $value[$field->getName()]; } - $rootCategoryId = isset($args['id']) ? (int)$args['id'] : + $rootCategoryId = !empty($args['id']) ? (int)$args['id'] : (int)$context->getExtensionAttributes()->getStore()->getRootCategoryId(); $this->checkCategoryIsActive->execute($rootCategoryId); From 3df7743cd932887db6d6093b875fe49ba1159073 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Tue, 27 Aug 2019 16:58:50 -0500 Subject: [PATCH 470/841] magento/graphql-ce#816: Root category ID --- .../Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php | 7 ++++--- .../CatalogGraphQl/Model/Resolver/RootCategoryId.php | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index b726615b2f4d7..74bc1a459aa50 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -13,6 +13,7 @@ use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CategoryTree as CategoryTreeDataProvider; /** * Category tree field resolver, used for GraphQL request processing. @@ -25,7 +26,7 @@ class CategoryTree implements ResolverInterface const CATEGORY_INTERFACE = 'CategoryInterface'; /** - * @var Products\DataProvider\CategoryTree + * @var CategoryTreeDataProvider */ private $categoryTree; @@ -40,12 +41,12 @@ class CategoryTree implements ResolverInterface private $checkCategoryIsActive; /** - * @param Products\DataProvider\CategoryTree $categoryTree + * @param CategoryTreeDataProvider $categoryTree * @param ExtractDataFromCategoryTree $extractDataFromCategoryTree * @param CheckCategoryIsActive $checkCategoryIsActive */ public function __construct( - Products\DataProvider\CategoryTree $categoryTree, + CategoryTreeDataProvider $categoryTree, ExtractDataFromCategoryTree $extractDataFromCategoryTree, CheckCategoryIsActive $checkCategoryIsActive ) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php index add47e3107ec9..4b3e0a1a58dfd 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/RootCategoryId.php @@ -21,6 +21,6 @@ class RootCategoryId implements ResolverInterface */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { - return $context->getExtensionAttributes()->getStore()->getRootCategoryId(); + return (int)$context->getExtensionAttributes()->getStore()->getRootCategoryId(); } } From 15d7a2d3260523479dc7fc60c8e07268a1efd370 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Tue, 27 Aug 2019 23:46:23 +0100 Subject: [PATCH 471/841] Update Magento_AuthorizenetAcceptjs Readme --- .../Magento/AuthorizenetAcceptjs/README.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/code/Magento/AuthorizenetAcceptjs/README.md b/app/code/Magento/AuthorizenetAcceptjs/README.md index b066f8a2d7509..7ebb69b5bf756 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/README.md +++ b/app/code/Magento/AuthorizenetAcceptjs/README.md @@ -1 +1,29 @@ +# Magento_AuthorizenetAcceptjs module + The Magento_AuthorizenetAcceptjs module implements the integration with the Authorize.Net payment gateway and makes the latter available as a payment method in Magento. + +## Installation details + +Before disabling or uninstalling this module, note that the `Magento_AuthorizenetCardinal` module depends on this module. + +For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html). + +## Structure + +`Gateway/` - the directory that contains payment gateway command interfaces and service classes. + +For information about typical file structure of a module in Magento 2, see [Module file structure](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/build/module-file-structure.html#module-file-structure). + +## Extensibility + +Extension developers can interact with the Magento_AuthorizenetAcceptjs module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AuthorizenetAcceptjs module. + +### Events + +This module observes the following events: + +`payment_method_assign_data_authorizenet_acceptjs` event in the `Magento\AuthorizenetAcceptjs\Observer\DataAssignObserver` file. + +For information about an event in Magento 2, see [Events and observers](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). From b264ac75a39440e4a814659184795276785e2030 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@users.noreply.github.com> Date: Wed, 28 Aug 2019 00:10:42 +0100 Subject: [PATCH 472/841] Update README.md --- app/code/Magento/AuthorizenetAcceptjs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/AuthorizenetAcceptjs/README.md b/app/code/Magento/AuthorizenetAcceptjs/README.md index 7ebb69b5bf756..2d8b296e0dc76 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/README.md +++ b/app/code/Magento/AuthorizenetAcceptjs/README.md @@ -24,6 +24,6 @@ Extension developers can interact with the Magento_AuthorizenetAcceptjs module. This module observes the following events: -`payment_method_assign_data_authorizenet_acceptjs` event in the `Magento\AuthorizenetAcceptjs\Observer\DataAssignObserver` file. +- `payment_method_assign_data_authorizenet_acceptjs` event in the `Magento\AuthorizenetAcceptjs\Observer\DataAssignObserver` file. For information about an event in Magento 2, see [Events and observers](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). From 580acb247428ac715b66a32206f2c0b16aec592d Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@users.noreply.github.com> Date: Wed, 28 Aug 2019 00:26:17 +0100 Subject: [PATCH 473/841] Update README.md --- app/code/Magento/AuthorizenetAcceptjs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/AuthorizenetAcceptjs/README.md b/app/code/Magento/AuthorizenetAcceptjs/README.md index 2d8b296e0dc76..b507f97a5a223 100644 --- a/app/code/Magento/AuthorizenetAcceptjs/README.md +++ b/app/code/Magento/AuthorizenetAcceptjs/README.md @@ -26,4 +26,4 @@ This module observes the following events: - `payment_method_assign_data_authorizenet_acceptjs` event in the `Magento\AuthorizenetAcceptjs\Observer\DataAssignObserver` file. -For information about an event in Magento 2, see [Events and observers](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). +For information about an event in Magento 2, see [Events and observers](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). From 68c2a5f171f36e6ad2e2461c2b5dc0d19b50b001 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Tue, 27 Aug 2019 23:54:42 +0100 Subject: [PATCH 474/841] Update Magento_AuthorizenetCardinal ReadMe --- .../Magento/AuthorizenetCardinal/README.md | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/AuthorizenetCardinal/README.md b/app/code/Magento/AuthorizenetCardinal/README.md index 2324f680bafc9..620dfbfc722de 100644 --- a/app/code/Magento/AuthorizenetCardinal/README.md +++ b/app/code/Magento/AuthorizenetCardinal/README.md @@ -1 +1,23 @@ -The AuthorizenetCardinal module provides a possibility to enable 3-D Secure 2.0 support for AuthorizenetAcceptjs payment integration. \ No newline at end of file +# Magento_AuthorizenetCardinal module + +The Magento_AuthorizenetCardinal module provides a possibility to enable 3D Secure 2.0 support for AuthorizenetAcceptjs payment integration. + +## Structure + +`Gateway/` - the directory that contains payment gateway command interfaces and service classes. + +For information about typical file structure of a module in Magento 2, see [Module file structure](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/build/module-file-structure.html#module-file-structure). + +## Extensibility + +Extension developers can interact with the Magento_AuthorizenetCardinal module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AuthorizenetCardinal module. + +### Events + +This module observes the following events: + +- `payment_method_assign_data_authorizenet_acceptjs` event in the `Magento\AuthorizenetCardinal\Observer\DataAssignObserver` file. + +For information about an event in Magento 2, see [Events and observers](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). From 429bf908e8c44734e565b568122a3b07aab14c1f Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Wed, 28 Aug 2019 00:38:47 +0100 Subject: [PATCH 475/841] Update Magento_AuthorizenetGraphQl ReadMe --- app/code/Magento/AuthorizenetGraphQl/README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/AuthorizenetGraphQl/README.md b/app/code/Magento/AuthorizenetGraphQl/README.md index 8b920e569341f..2af2b6a1024af 100644 --- a/app/code/Magento/AuthorizenetGraphQl/README.md +++ b/app/code/Magento/AuthorizenetGraphQl/README.md @@ -1,3 +1,9 @@ -# AuthorizenetGraphQl +# Magento_AuthorizenetGraphQl module - **AuthorizenetGraphQl** defines the data types needed to pass payment information data from the client to Magento. +The Magento_AuthorizenetGraphQl module defines the data types needed to pass payment information data from the client to Magento. + +## Extensibility + +Extension developers can interact with the Magento_AuthorizenetGraphQl module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AuthorizenetGraphQl module. From 18a83d6de6d850a732d16aed2c95fb99dbcd6702 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Tue, 27 Aug 2019 14:29:20 -0500 Subject: [PATCH 476/841] MC-19542: Error While placing order from Paypal through Braintree --- .../Model/Paypal/Helper/OrderPlace.php | 13 +++---- .../Braintree/Plugin/OrderCancellation.php | 4 +- .../Controller/Paypal/PlaceOrderTest.php | 38 ++++++++++++++++++- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php index 314404c79939c..f448cd4c19785 100644 --- a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php +++ b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php @@ -19,6 +19,7 @@ /** * Class OrderPlace * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class OrderPlace extends AbstractHelper { @@ -79,9 +80,10 @@ public function __construct( public function execute(Quote $quote, array $agreement) { if (!$this->agreementsValidator->isValid($agreement)) { - throw new LocalizedException(__( + $errorMsg = __( "The order wasn't placed. First, agree to the terms and conditions, then try placing your order again." - )); + ); + throw new LocalizedException($errorMsg); } if ($this->getCheckoutMethod($quote) === Onepage::METHOD_GUEST) { @@ -91,12 +93,7 @@ public function execute(Quote $quote, array $agreement) $this->disabledQuoteAddressValidation($quote); $quote->collectTotals(); - try { - $this->cartManagement->placeOrder($quote->getId()); - } catch (\Exception $e) { - $this->orderCancellationService->execute($quote->getReservedOrderId()); - throw $e; - } + $this->cartManagement->placeOrder($quote->getId()); } /** diff --git a/app/code/Magento/Braintree/Plugin/OrderCancellation.php b/app/code/Magento/Braintree/Plugin/OrderCancellation.php index 90c72839d9777..4754e89e67ada 100644 --- a/app/code/Magento/Braintree/Plugin/OrderCancellation.php +++ b/app/code/Magento/Braintree/Plugin/OrderCancellation.php @@ -72,7 +72,9 @@ public function aroundPlaceOrder( ]; if (in_array($payment->getMethod(), $paymentCodes)) { $incrementId = $quote->getReservedOrderId(); - $this->orderCancellationService->execute($incrementId); + if ($incrementId) { + $this->orderCancellationService->execute($incrementId); + } } throw $e; diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/PlaceOrderTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/PlaceOrderTest.php index 3c4fcdd1fc58c..756e5bae36e28 100644 --- a/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/PlaceOrderTest.php +++ b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/PlaceOrderTest.php @@ -76,7 +76,6 @@ protected function tearDown() /** * Tests a negative scenario for a place order flow when exception throws after placing an order. * - * * @magentoAppArea frontend * @magentoAppIsolation enabled * @magentoDataFixture Magento/Braintree/Fixtures/paypal_quote.php @@ -113,6 +112,43 @@ public function testExecuteWithFailedOrder() self::assertEquals('canceled', $order->getState()); } + /** + * Tests a negative scenario for a place order flow when exception throws before order creation. + * + * @magentoAppArea frontend + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Braintree/Fixtures/paypal_quote.php + */ + public function testExecuteWithFailedQuoteValidation() + { + $reservedOrderId = null; + $quote = $this->getQuote('test01'); + $quote->setReservedOrderId($reservedOrderId); + + $this->session->method('getQuote') + ->willReturn($quote); + $this->session->method('getQuoteId') + ->willReturn($quote->getId()); + + $this->adapter->method('sale') + ->willReturn($this->getTransactionStub('authorized')); + $this->adapter->method('void') + ->willReturn($this->getTransactionStub('voided')); + + // emulates an error after placing the order + $this->session->method('setLastOrderStatus') + ->willThrowException(new \Exception('Test Exception')); + + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); + $this->dispatch('braintree/paypal/placeOrder'); + + self::assertRedirect(self::stringContains('checkout/cart')); + self::assertSessionMessages( + self::equalTo(['The order #' . $reservedOrderId . ' cannot be processed.']), + MessageInterface::TYPE_ERROR + ); + } + /** * Gets quote by reserved order ID. * From 73f98af4e06f30b1a940fa958b52e5e350906f3b Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Tue, 27 Aug 2019 22:26:38 -0500 Subject: [PATCH 477/841] MC-19432: REST API returns 404 error instead of 400 --- .../testsuite/Magento/Quote/Api/CartItemRepositoryTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php index a0dc9a8f80dc8..402b5e730009b 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php @@ -138,6 +138,9 @@ public function testFailedAddItem(?array $cartItem) $requestData = [ 'cartItem' => $cartItem, ]; + + $this->expectException(\Exception::class); + $this->expectExceptionCode(TESTS_WEB_API_ADAPTER === self::ADAPTER_SOAP ? 0 : 400); $this->_webApiCall($serviceInfo, $requestData); } From f98027b62c79394dbb1e5aa0cd44ec26e637c09a Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Tue, 27 Aug 2019 23:24:30 -0500 Subject: [PATCH 478/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix static failures tests and added fixture for sort by relevance --- .../GraphQl/Catalog/ProductSearchTest.php | 104 +++++++++++++++--- .../GraphQl/Catalog/ProductViewTest.php | 3 +- .../_files/products_for_relevance_sorting.php | 75 +++++++++++++ ...th_layered_navigation_custom_attribute.php | 3 +- 4 files changed, 169 insertions(+), 16 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 169aba7c15734..b6c02f0161ae2 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -116,7 +116,7 @@ public function testLayeredNavigationWithConfigurableChildrenOutOfStock() $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); $response = $this->graphQlQuery($query); - // 1 product is returned since only one child product with attribute option1 from 1st Configurable product is OOS + // Out of two children, only one child product of 1st Configurable product with option1 is OOS $this->assertEquals(1, $response['products']['total_count']); // Custom attribute filter layer data @@ -269,7 +269,8 @@ public function testFilterProductsByMultiSelectCustomAttribute() /** @var AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); array_shift($options); - $optionValues = array(); + $optionValues = []; + // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall for ($i = 0; $i < count($options); $i++) { $optionValues[] = $options[$i]->getValue(); } @@ -411,7 +412,7 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() $layers[$layerIndex][0]['request_var'], $response['products']['filters'][$layerIndex]['request_var'], 'request_var does not match' - ) ; + ); } // Validate the price filter layer data from the response @@ -490,7 +491,7 @@ public function testFilterByCategoryIdAndCustomAttribute() $this->assertEquals(2, $response['products']['total_count']); $actualCategoryFilterItems = $response['products']['filters'][1]['filter_items']; //Validate the number of categories/sub-categories that contain the products with the custom attribute - $this->assertCount(6,$actualCategoryFilterItems); + $this->assertCount(6, $actualCategoryFilterItems); $expectedCategoryFilterItems = [ @@ -527,7 +528,7 @@ public function testFilterByCategoryIdAndCustomAttribute() $categoryFilterItems[$index][0]['items_count'], $actualCategoryFilterItems[$index]['items_count'], 'Products count in the category is incorrect' - ) ; + ); } } /** @@ -972,7 +973,7 @@ public function testFilteringForProductsFromMultipleCategories() } /** - * Filter products by category only + * Filter products by single category * * @magentoApiDataFixture Magento/Catalog/_files/product_in_multiple_categories.php * @return void @@ -1056,6 +1057,58 @@ public function testFilterProductsBySingleCategoryId() } } + /** + * Sorting the search results by relevance (DESC => most relevant) + * + * @magentoApiDataFixture Magento/Catalog/_files/product_in_multiple_categories.php + * @return void + */ + public function testFilterProductsAndSortByRelevance() + { + $search_term ="red white blue grey socks"; + $query + = <<<QUERY +{ + products( + search:"{$search_term}" + + sort:{relevance:DESC} + pageSize: 5 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + + } + + } + +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(2, $response['products']['total_count']); + } + /** * Sorting by price in the DESC order from the filtered items with default pageSize * @@ -1064,7 +1117,6 @@ public function testFilterProductsBySingleCategoryId() */ public function testQuerySortByPriceDESCWithDefaultPageSize() { - $query = <<<QUERY { @@ -1182,13 +1234,30 @@ public function testProductBasicFullTextSearchQuery() } /** + * @magentoApiDataFixture Magento/Catalog/_files/category.php * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php */ public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + + $prod1 = $productRepository->get('simple2'); + $prod2 = $productRepository->get('simple1'); + $filteredProducts = [$prod1, $prod2]; + /** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ + $categoryLinkManagement = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); + foreach ($filteredProducts as $product) { + $categoryLinkManagement->assignProductToCategories( + $product->getSku(), + [333] + ); + } + $query =<<<QUERY - { +{ products( filter: { @@ -1234,6 +1303,12 @@ public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() type_id } total_count + filters + { + request_var + name + filter_items_count + } page_info { page_size @@ -1242,15 +1317,16 @@ public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() } } QUERY; + $response = $this->graphQlQuery($query); $this->assertEquals(2, $response['products']['total_count']); - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - - $prod1 = $productRepository->get('simple2'); - $prod2 = $productRepository->get('simple1'); - $filteredProducts = [$prod1, $prod2]; $this->assertProductItemsWithPriceCheck($filteredProducts, $response); + //verify that by default Price and category are the only layers available + $filterNames = ['Price', 'Category']; + $this->assertCount(2, $response['products']['filters'], 'Filter count does not match'); + for ($i = 0; $i < count($response['products']['filters']); $i++) { + $this->assertEquals($filterNames[$i], $response['products']['filters'][$i]['name']); + } } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index 5990211f1e47d..378b87fb9591f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -795,6 +795,7 @@ private function assertOptions($product, $actualResponse) ]; $this->assertResponseFields($value, $assertionMapValues); } else { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $assertionMap = array_merge( $assertionMap, [ @@ -823,7 +824,7 @@ private function assertOptions($product, $actualResponse) $valueKeyName = 'date_option'; $valueAssertionMap = []; } - + // phpcs:ignore Magento2.Performance.ForeachArrayMerge $valueAssertionMap = array_merge( $valueAssertionMap, [ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php new file mode 100644 index 0000000000000..e25bd21b7683d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../Framework/Search/_files/products.php'; +use Magento\Catalog\Api\ProductRepositoryInterface; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$category = $objectManager->create(\Magento\Catalog\Model\Category::class); +$category->isObjectNew(true); +$category->setId( + 333 +)->setCreatedAt( + '2019-08-27 11:05:07' +)->setName( + 'Colorful Category' +)->setParentId( + 2 +)->setPath( + '1/2/300' +)->setLevel( + 2 +)->setAvailableSortBy( + ['position', 'name'] +)->setDefaultSortBy( + 'name' +)->setIsActive( + true +)->setPosition( + 1 +)->save(); + +$defaultAttributeSet = $objectManager->get(Magento\Eav\Model\Config::class) + ->getEntityType('catalog_product') + ->getDefaultAttributeSetId(); + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId($defaultAttributeSet) + ->setStoreId(1) + ->setWebsiteIds([1]) + ->setName('Red White and Blue striped Shoes') + ->setSku('red white and blue striped shoes') + ->setPrice(40) + ->setWeight(8) + ->setDescription('Red white and blue flip flops at <b>one</b>') + ->setMetaTitle('Multi colored shoes meta title') + ->setMetaKeyword('red, white,flip-flops, women, kids') + ->setMetaDescription('flip flops women kids meta description') + ->setStockData(['use_config_manage_stock' => 0]) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->save(); +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$skus = ['green_socks', 'white_shorts','red_trousers','blue_briefs','grey_shorts', 'red white and blue striped shoes' ]; +$products = []; +foreach ($skus as $sku) { + $products = $productRepository->get($sku); +} +/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ + $categoryLinkManagement = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); +foreach ($products as $product) { + $categoryLinkManagement->assignProductToCategories( + $product->getSku(), + [300] + ); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php index 6cd2819c2d296..864a931359bdd 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -115,7 +115,8 @@ 'catalog_product', $attributeSet->getId(), $attributeSet->getDefaultGroupId(), - $attribute1->getId()); + $attribute1->getId() + ); } $eavConfig->clear(); From d01976a7f8984577c04c0bc15379fc5ddf9265da Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Wed, 28 Aug 2019 00:14:12 -0500 Subject: [PATCH 479/841] MC-19432: REST API returns 404 error instead of 400 --- .../Quote/Api/CartItemRepositoryTest.php | 59 ++----------------- 1 file changed, 4 insertions(+), 55 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php index 402b5e730009b..15e10196f878d 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartItemRepositoryTest.php @@ -6,7 +6,6 @@ */ namespace Magento\Quote\Api; -use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\CustomOptions\CustomOptionProcessor; use Magento\Framework\Webapi\Rest\Request; use Magento\Quote\Model\Quote; @@ -84,10 +83,9 @@ public function testGetList() */ public function testAddItem() { - $productSku = 'custom-design-simple-product'; - $productRepository = $this->objectManager->create(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku); - + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class)->load(2); + $productSku = $product->getSku(); /** @var Quote $quote */ $quote = $this->objectManager->create(Quote::class); $quote->load('test_order_1', 'reserved_order_id'); @@ -112,59 +110,10 @@ public function testAddItem() ], ]; $this->_webApiCall($serviceInfo, $requestData); - $this->assertTrue($quote->hasProductId($product->getId())); + $this->assertTrue($quote->hasProductId(2)); $this->assertEquals(7, $quote->getItemByProduct($product)->getQty()); } - /** - * @expectedException \Exception - * @expectedExceptionCode 400 - * @dataProvider failedAddItemDataProvider - * @param array|null $cartItem - */ - public function testFailedAddItem(?array $cartItem) - { - $serviceInfo = [ - 'rest' => [ - 'resourcePath' => self::RESOURCE_PATH . 'mine/items', - 'httpMethod' => Request::HTTP_METHOD_POST, - ], - 'soap' => [ - 'service' => self::SERVICE_NAME, - 'serviceVersion' => self::SERVICE_VERSION, - 'operation' => self::SERVICE_NAME . 'Save', - ], - ]; - $requestData = [ - 'cartItem' => $cartItem, - ]; - - $this->expectException(\Exception::class); - $this->expectExceptionCode(TESTS_WEB_API_ADAPTER === self::ADAPTER_SOAP ? 0 : 400); - $this->_webApiCall($serviceInfo, $requestData); - } - - /** - * @return array - */ - public function failedAddItemDataProvider(): array - { - return [ - 'absent cart item' => [ - null, - ], - 'empty cart item' => [ - [], - ], - 'absent cart id' => [ - [ - 'sku' => 'custom-design-simple-product', - 'qty' => 7, - ], - ], - ]; - } - /** * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php */ From 33f217e0b5ff825fe688cf496e84c3188e7d18ff Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Wed, 28 Aug 2019 08:32:03 +0300 Subject: [PATCH 480/841] graphQl-814: remove a configurable item from cart test coverage --- .../RemoveConfigurableProductFromCartTest.php | 114 ++++++++++++++++++ .../quote_with_configurable_product.php | 8 ++ 2 files changed, 122 insertions(+) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/RemoveConfigurableProductFromCartTest.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/RemoveConfigurableProductFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/RemoveConfigurableProductFromCartTest.php new file mode 100644 index 0000000000000..31308eaef5acc --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/RemoveConfigurableProductFromCartTest.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\ConfigurableProduct; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Quote\Model\Quote\Item; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Remove configurable product from cart testcases + */ +class RemoveConfigurableProductFromCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->quoteFactory = $objectManager->get(QuoteFactory::class); + $this->quoteResource = $objectManager->get(QuoteResource::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/quote_with_configurable_product.php + */ + public function testRemoveConfigurableProductFromCart() + { + $configurableOptionSku = 'simple_10'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_cart_with_configurable'); + $quoteItemId = $this->getQuoteItemIdBySku($configurableOptionSku); + $query = $this->getQuery($maskedQuoteId, $quoteItemId); + $response = $this->graphQlMutation($query); + + $this->assertArrayHasKey('cart', $response['removeItemFromCart']); + $this->assertArrayHasKey('items', $response['removeItemFromCart']['cart']); + $this->assertEquals(0, count($response['removeItemFromCart']['cart']['items'])); + } + + /** + * @param string $maskedQuoteId + * @param int $itemId + * @return string + */ + private function getQuery(string $maskedQuoteId, int $itemId): string + { + return <<<QUERY +mutation { + removeItemFromCart( + input: { + cart_id: "{$maskedQuoteId}" + cart_item_id: {$itemId} + } + ) { + cart { + items { + quantity + } + } + } +} +QUERY; + } + + /** + * Returns quote item ID by product's SKU + * + * @param string $sku + * @return int + */ + private function getQuoteItemIdBySku(string $sku): int + { + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, 'test_cart_with_configurable', 'reserved_order_id'); + /** @var Item $quoteItem */ + $quoteItemsCollection = $quote->getItemsCollection(); + foreach ($quoteItemsCollection->getItems() as $item) { + if ($item->getSku() == $sku) { + return (int)$item->getId(); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php index 150ce3b3108e5..7f108623f02f2 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/quote_with_configurable_product.php @@ -40,3 +40,11 @@ /** @var $objectManager \Magento\TestFramework\ObjectManager */ $objectManager = Bootstrap::getObjectManager(); $objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); +$quoteIdMask->setQuoteId($cart->getQuote()->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); From 25e1590db0e465bc03e601a82c53890f9f3f4b6b Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Wed, 28 Aug 2019 11:32:42 +0300 Subject: [PATCH 481/841] graphQl-810: test add non existent configurable product --- .../AddConfigurableProductToCartTest.php | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php index 0e334999599b4..8bce845f77591 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php @@ -92,6 +92,53 @@ public function testAddProductIfQuantityIsNotAvailable() $this->graphQlMutation($query); } + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable_sku.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @expectedException \Exception + * @expectedExceptionMessage Could not find a product with SKU "configurable_no_exist" + */ + public function testAddNonExistentConfigurableProductParentToCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $parentSku = 'configurable_no_exist'; + $sku = 'simple_20'; + + $query = $this->getQuery( + $maskedQuoteId, + $parentSku, + $sku, + 2000 + ); + + $this->graphQlMutation($query); + } + + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable_sku.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * @expectedException \Exception + * @expectedExceptionMessage Could not add the product with SKU configurable to the shopping cart: Could not find specified product. + */ + public function testAddNonExistentConfigurableProductVariationToCart() + { + $searchResponse = $this->graphQlQuery($this->getFetchProductQuery('configurable')); + $product = current($searchResponse['products']['items']); + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $parentSku = $product['sku']; + $sku = 'simple_no_exist'; + + $query = $this->getQuery( + $maskedQuoteId, + $parentSku, + $sku, + 2000 + ); + + $this->graphQlMutation($query); + } + /** * @param string $maskedQuoteId * @param string $parentSku From a234f2c121af000b7b5adc08224cfac0a0d59af4 Mon Sep 17 00:00:00 2001 From: Geeta <geeta@ranosys.com> Date: Fri, 23 Aug 2019 18:40:55 +0530 Subject: [PATCH 482/841] Fix(21450): Revert all changes and resolve issue --- .../Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js index d74105fe531e4..fc4809d381967 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js @@ -135,7 +135,7 @@ define([ // trigger global event, so other modules will be able add parameters to redirect url $('body').trigger('catalogCategoryAddToCartRedirect', eventData); - if (eventData.redirectParameters.length > 0) { + if (eventData.redirectParameters.length > 0 && window.location.href === res.backUrl) { parameters = res.backUrl.split('#'); parameters.push(eventData.redirectParameters.join('&')); res.backUrl = parameters.join('#'); From de8028e3db97d85705500e7c14dd629a40ae08af Mon Sep 17 00:00:00 2001 From: Sergey Solo <ssolomakhin@magecom.us> Date: Wed, 28 Aug 2019 14:31:24 +0300 Subject: [PATCH 483/841] github-issue; #2228; Encode xml entities in tag attributes --- lib/internal/Magento/Framework/Simplexml/Element.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Simplexml/Element.php b/lib/internal/Magento/Framework/Simplexml/Element.php index eec48bc555010..1c039514d836c 100644 --- a/lib/internal/Magento/Framework/Simplexml/Element.php +++ b/lib/internal/Magento/Framework/Simplexml/Element.php @@ -245,7 +245,7 @@ public function asNiceXml($filename = '', $level = 0) $attributes = $this->attributes(); if ($attributes) { foreach ($attributes as $key => $value) { - $out .= ' ' . $key . '="' . str_replace('"', '\"', (string)$value) . '"'; + $out .= ' ' . $key . '="' . str_replace('"', '\"', $this->xmlentities($value)) . '"'; } } From a918fa939e4be7f7e0227ed973bde3023ee5a0e2 Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Wed, 28 Aug 2019 14:48:11 +0300 Subject: [PATCH 484/841] graphQl-825: Add downloadable product to Cart with customizable options --- ...ableProductWithCustomOptionsToCartTest.php | 192 ++++++++++++++++++ ...mpleProductWithCustomOptionsToCartTest.php | 43 +--- ...tualProductWithCustomOptionsToCartTest.php | 44 +--- .../GetCustomOptionsValuesForQueryBySku.php | 65 ++++++ ...oduct_downloadable_with_custom_options.php | 98 +++++++++ ...nloadable_with_custom_options_rollback.php | 8 + 6 files changed, 377 insertions(+), 73 deletions(-) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php create mode 100644 dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php create mode 100644 dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php new file mode 100644 index 0000000000000..d0348baadddcc --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php @@ -0,0 +1,192 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\ObjectManager\ObjectManager; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test cases for adding downloadable product with custom options to cart. + */ +class AddDownloadableProductWithCustomOptionsToCartTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var GetCustomOptionsValuesForQueryBySku + */ + private $getCustomOptionsValuesForQueryBySku; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $this->objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->getCustomOptionsValuesForQueryBySku = $this->objectManager->get(GetCustomOptionsValuesForQueryBySku::class); + } + + /** + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_custom_options.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testAddDownloadableProductWithOptions() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + + $sku = 'downloadable-product-with-purchased-separately-links'; + $qty = 1; + $links = $this->getProductsLinks($sku); + $linkId = key($links); + + $customOptionsValues = $this->getCustomOptionsValuesForQueryBySku->execute($sku); + /* Generate customizable options fragment for GraphQl request */ + $queryCustomizableOptionValues = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); + $customizableOptions = "customizable_options: {$queryCustomizableOptionValues}"; + + $query = $this->getQuery($maskedQuoteId, $qty, $sku, $customizableOptions, $linkId); + + $response = $this->graphQlMutation($query); + self::assertArrayHasKey('items', $response['addDownloadableProductsToCart']['cart']); + self::assertCount($qty, $response['addDownloadableProductsToCart']['cart']); + $customizableOptionsOutput = $response['addDownloadableProductsToCart']['cart']['items'][0]['customizable_options']; + $assignedOptionsCount = count($customOptionsValues); + for ($counter = 0; $counter < $assignedOptionsCount; $counter++) { + $expectedValues = $this->buildExpectedValuesArray($customOptionsValues[$counter]['value_string']); + self::assertEquals( + $expectedValues, + $customizableOptionsOutput[$counter]['values'] + ); + } + } + + /** + * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_custom_options.php + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + * + * @expectedException \Exception + * @expectedExceptionMessage The product's required option(s) weren't entered. Make sure the options are entered and try again. + */ + public function testAddDownloadableProductWithMissedRequiredOptionsSet() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + + $sku = 'downloadable-product-with-purchased-separately-links'; + $qty = 1; + $links = $this->getProductsLinks($sku); + $linkId = key($links); + $customizableOptions = ''; + + $query = $this->getQuery($maskedQuoteId, $qty, $sku, $customizableOptions, $linkId); + + $this->graphQlMutation($query); + } + + /** + * Function returns array of all product's links + * + * @param string $sku + * @return array + */ + private function getProductsLinks(string $sku) : array + { + $result = []; + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + $product = $productRepository->get($sku, false, null, true); + + foreach ($product->getDownloadableLinks() as $linkObject) { + $result[$linkObject->getLinkId()] = [ + 'title' => $linkObject->getTitle(), + 'link_type' => null, //deprecated field + 'price' => $linkObject->getPrice(), + ]; + } + + return $result; + } + + /** + * Build the part of expected response. + * + * @param string $assignedValue + * @return array + */ + private function buildExpectedValuesArray(string $assignedValue) : array + { + $assignedOptionsArray = explode(',', trim($assignedValue, '[]')); + $expectedArray = []; + foreach ($assignedOptionsArray as $assignedOption) { + $expectedArray[] = ['value' => $assignedOption]; + } + return $expectedArray; + } + + /** + * Returns GraphQl query string + * + * @param string $maskedQuoteId + * @param int $qty + * @param string $sku + * @param string $customizableOptions + * @param $linkId + * @return string + */ + private function getQuery(string $maskedQuoteId, int $qty, string $sku, string $customizableOptions, $linkId): string + { + $query = <<<MUTATION +mutation { + addDownloadableProductsToCart( + input: { + cart_id: "{$maskedQuoteId}", + cart_items: [ + { + data: { + quantity: {$qty}, + sku: "{$sku}" + }, + {$customizableOptions} + downloadable_product_links: [ + { + link_id: {$linkId} + } + ] + } + ] + } + ) { + cart { + items { + quantity + ... on DownloadableCartItem { + customizable_options { + label + values { + value + } + } + } + } + } + } +} +MUTATION; + return $query; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php index f60194d8df324..b0b116b0cddad 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductWithCustomOptionsToCartTest.php @@ -26,6 +26,11 @@ class AddSimpleProductWithCustomOptionsToCartTest extends GraphQlAbstract */ private $productCustomOptionsRepository; + /** + * @var GetCustomOptionsValuesForQueryBySku + */ + private $getCustomOptionsValuesForQueryBySku; + /** * @inheritdoc */ @@ -34,6 +39,7 @@ protected function setUp() $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); $this->productCustomOptionsRepository = $objectManager->get(ProductCustomOptionRepositoryInterface::class); + $this->getCustomOptionsValuesForQueryBySku = $objectManager->get(GetCustomOptionsValuesForQueryBySku::class); } /** @@ -49,7 +55,7 @@ public function testAddSimpleProductWithOptions() $quantity = 1; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $customOptionsValues = $this->getCustomOptionsValuesForQuery($sku); + $customOptionsValues = $this->getCustomOptionsValuesForQueryBySku->execute($sku); /* Generate customizable options fragment for GraphQl request */ $queryCustomizableOptionValues = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); @@ -135,41 +141,6 @@ private function getQuery(string $maskedQuoteId, string $sku, float $quantity, s QUERY; } - /** - * Generate an array with test values for customizable options - * based on the option type - * - * @param string $sku - * @return array - */ - private function getCustomOptionsValuesForQuery(string $sku): array - { - $customOptions = $this->productCustomOptionsRepository->getList($sku); - $customOptionsValues = []; - - foreach ($customOptions as $customOption) { - $optionType = $customOption->getType(); - if ($optionType == 'field' || $optionType == 'area') { - $customOptionsValues[] = [ - 'id' => (int)$customOption->getOptionId(), - 'value_string' => 'test' - ]; - } elseif ($optionType == 'drop_down') { - $optionSelectValues = $customOption->getValues(); - $customOptionsValues[] = [ - 'id' => (int)$customOption->getOptionId(), - 'value_string' => reset($optionSelectValues)->getOptionTypeId() - ]; - } elseif ($optionType == 'multiple') { - $customOptionsValues[] = [ - 'id' => (int)$customOption->getOptionId(), - 'value_string' => '[' . implode(',', array_keys($customOption->getValues())) . ']' - ]; - } - } - return $customOptionsValues; - } - /** * Build the part of expected response. * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php index 7c49ab86120d8..a8088b0b46b87 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddVirtualProductWithCustomOptionsToCartTest.php @@ -26,6 +26,11 @@ class AddVirtualProductWithCustomOptionsToCartTest extends GraphQlAbstract */ private $productCustomOptionsRepository; + /** + * @var GetCustomOptionsValuesForQueryBySku + */ + private $getCustomOptionsValuesForQueryBySku; + /** * @inheritdoc */ @@ -34,6 +39,7 @@ protected function setUp() $objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); $this->productCustomOptionsRepository = $objectManager->get(ProductCustomOptionRepositoryInterface::class); + $this->getCustomOptionsValuesForQueryBySku = $objectManager->get(GetCustomOptionsValuesForQueryBySku::class); } /** @@ -49,7 +55,7 @@ public function testAddVirtualProductWithOptions() $quantity = 1; $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); - $customOptionsValues = $this->getCustomOptionsValuesForQuery($sku); + $customOptionsValues = $this->getCustomOptionsValuesForQueryBySku->execute($sku); /* Generate customizable options fragment for GraphQl request */ $queryCustomizableOptionValues = preg_replace('/"([^"]+)"\s*:\s*/', '$1:', json_encode($customOptionsValues)); @@ -135,42 +141,6 @@ private function getQuery(string $maskedQuoteId, string $sku, float $quantity, s QUERY; } - /** - * Generate an array with test values for customizable options - * based on the option type - * - * @param string $sku - * @return array - */ - private function getCustomOptionsValuesForQuery(string $sku): array - { - $customOptions = $this->productCustomOptionsRepository->getList($sku); - $customOptionsValues = []; - - foreach ($customOptions as $customOption) { - $optionType = $customOption->getType(); - if ($optionType == 'field' || $optionType == 'area') { - $customOptionsValues[] = [ - 'id' => (int)$customOption->getOptionId(), - 'value_string' => 'test' - ]; - } elseif ($optionType == 'drop_down') { - $optionSelectValues = $customOption->getValues(); - $customOptionsValues[] = [ - 'id' => (int)$customOption->getOptionId(), - 'value_string' => reset($optionSelectValues)->getOptionTypeId() - ]; - } elseif ($optionType == 'multiple') { - $customOptionsValues[] = [ - 'id' => (int)$customOption->getOptionId(), - 'value_string' => '[' . implode(',', array_keys($customOption->getValues())) . ']' - ]; - } - } - - return $customOptionsValues; - } - /** * Build the part of expected response. * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php new file mode 100644 index 0000000000000..58f193e6ca6e4 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; + +/** + * Generate an array with test values for customizable options + * based on the option type + */ +class GetCustomOptionsValuesForQueryBySku +{ + /** + * @var ProductCustomOptionRepositoryInterface + */ + private $productCustomOptionRepository; + + /** + * @param ProductCustomOptionRepositoryInterface $productCustomOptionRepository + */ + public function __construct(ProductCustomOptionRepositoryInterface $productCustomOptionRepository) + { + $this->productCustomOptionRepository = $productCustomOptionRepository; + } + + /** + * Returns array of custom options for the product + * + * @param string $sku + * @return array + */ + public function execute(string $sku): array + { + $customOptions = $this->productCustomOptionRepository->getList($sku); + $customOptionsValues = []; + + foreach ($customOptions as $customOption) { + $optionType = $customOption->getType(); + if ($optionType == 'field' || $optionType == 'area') { + $customOptionsValues[] = [ + 'id' => (int)$customOption->getOptionId(), + 'value_string' => 'test' + ]; + } elseif ($optionType == 'drop_down') { + $optionSelectValues = $customOption->getValues(); + $customOptionsValues[] = [ + 'id' => (int)$customOption->getOptionId(), + 'value_string' => reset($optionSelectValues)->getOptionTypeId() + ]; + } elseif ($optionType == 'multiple') { + $customOptionsValues[] = [ + 'id' => (int)$customOption->getOptionId(), + 'value_string' => '[' . implode(',', array_keys($customOption->getValues())) . ']' + ]; + } + } + + return $customOptionsValues; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php new file mode 100644 index 0000000000000..bf7a2600470c5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\TestFramework\Helper\Bootstrap as Bootstrap; + +require __DIR__ . '/product_downloadable_with_purchased_separately_links.php'; + +$product->setCanSaveCustomOptions(true); +$product->setHasOptions(true); + +$options = [ + [ + 'title' => 'test_option_code_1', + 'type' => 'field', + 'is_require' => true, + 'sort_order' => 1, + 'price' => -10.0, + 'price_type' => 'fixed', + 'sku' => 'sku1', + 'max_characters' => 10, + ], + [ + 'title' => 'area option', + 'type' => 'area', + 'is_require' => true, + 'sort_order' => 2, + 'price' => 20.0, + 'price_type' => 'percent', + 'sku' => 'sku2', + 'max_characters' => 20 + ], + [ + 'title' => 'drop_down option', + 'type' => 'drop_down', + 'is_require' => false, + 'sort_order' => 4, + 'values' => [ + [ + 'title' => 'drop_down option 1', + 'price' => 10, + 'price_type' => 'fixed', + 'sku' => 'drop_down option 1 sku', + 'sort_order' => 1, + ], + [ + 'title' => 'drop_down option 2', + 'price' => 20, + 'price_type' => 'fixed', + 'sku' => 'drop_down option 2 sku', + 'sort_order' => 2, + ], + ], + ], + [ + 'title' => 'multiple option', + 'type' => 'multiple', + 'is_require' => false, + 'sort_order' => 5, + 'values' => [ + [ + 'title' => 'multiple option 1', + 'price' => 10, + 'price_type' => 'fixed', + 'sku' => 'multiple option 1 sku', + 'sort_order' => 1, + ], + [ + 'title' => 'multiple option 2', + 'price' => 20, + 'price_type' => 'fixed', + 'sku' => 'multiple option 2 sku', + 'sort_order' => 2, + ], + ], + ] +]; + +$customOptions = []; + +/** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory */ +$customOptionFactory = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); + +foreach ($options as $option) { + /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterface $customOption */ + $customOption = $customOptionFactory->create(['data' => $option]); + $customOption->setProductSku($product->getSku()); + + $customOptions[] = $customOption; +} + +$product->setOptions($customOptions); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */ +$productRepositoryFactory = Bootstrap::getObjectManager()->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepositoryFactory->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options_rollback.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options_rollback.php new file mode 100644 index 0000000000000..4eef2a941f65f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Framework\Exception\NoSuchEntityException; + +require __DIR__ . '/product_downloadable_rollback.php'; From bc66643fa5b27c90689791458ca8487c20857202 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Wed, 28 Aug 2019 15:20:42 +0300 Subject: [PATCH 485/841] MC-19399: Import of Customizable Option price does not respect the website scope --- .../CatalogImportExport/Model/Import/ProductTest.php | 6 +++--- ...product_with_custom_options_and_multiple_store_views.csv | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 75ac6d3f9935b..7b4e3c6a87f55 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -375,8 +375,8 @@ public function testSaveCustomOptions(string $importFile, string $sku, int $expe /** * Tests adding of custom options with multiple store views * - * @magentoDataFixture Magento/Store/_files/second_store.php - * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoConfigFixture current_store catalog/price/scope 1 + * @magentoDataFixture Magento/Store/_files/core_second_third_fixturestore.php * @magentoAppIsolation enabled */ public function testSaveCustomOptionsWithMultipleStoreViews() @@ -387,7 +387,7 @@ public function testSaveCustomOptionsWithMultipleStoreViews() $storeCodes = [ 'admin', 'default', - 'fixture_second_store', + 'secondstore', ]; /** @var \Magento\Store\Model\StoreManagerInterface $storeManager */ $importFile = 'product_with_custom_options_and_multiple_store_views.csv'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv index cfd5b770d35d8..d4c4130e946a4 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_and_multiple_store_views.csv @@ -1,4 +1,4 @@ sku,website_code,store_view_code,attribute_set_code,product_type,name,description,short_description,weight,product_online,visibility,product_websites,categories,price,special_price,special_price_from_date,special_price_to_date,tax_class_name,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,additional_images,additional_image_labels,configurable_variation_labels,configurable_variations,bundle_price_type,bundle_sku_type,bundle_weight_type,bundle_values,downloadble_samples,downloadble_links,associated_skus,related_skus,crosssell_skus,upsell_skus,custom_options,additional_attributes,manage_stock,is_in_stock,qty,out_of_stock_qty,is_qty_decimal,allow_backorders,min_cart_qty,max_cart_qty,notify_on_stock_below,qty_increments,enable_qty_increments,is_decimal_divided,new_from_date,new_to_date,gift_message_available,created_at,updated_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_price,msrp_display_actual_price_type,map_enabled -simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search",base,,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 1,sku=3-1-select|name=Test Select,type=drop_down,required=1,price=3,option_title=Select Option 2,sku=3-2-select|name=Test Field Title,type=field,required=1,sku=1-text,price=0,price_type=fixed,max_characters=10|name=Test Date and Time Title,type=date_time,required=1,sku=2-date|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 1,sku=4-1-select|name=Test Checkbox,type=checkbox,required=1,price=3,option_title=Checkbox Option 2,sku=4-2-select|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 1,sku=5-1-radio|name=Test Radio,type=radio,required=1,price=3,option_title=Radio Option 2,sku=5-2-radio",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, -simple,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select_default,type=drop_down,option_title=Select Option 1_default|name=Test Select_default,type=drop_down,option_title=Select Option 2_default|name=Test Field Title_default,type=field|name=Test Date and Time Title_default,type=date_time|name=Test Checkbox_default,type=checkbox,option_title=Checkbox Option 1_default|name=Test Checkbox_default,type=checkbox,option_title=Checkbox Option 2_default|name=Test Radio_default,type=radio,option_title=Radio Option 1_default|name=Test Radio_default,type=radio,option_title=Radio Option 2_default",,,,,,,,,,,,,,,,,,,,,,,,,,, -simple,,fixture_second_store,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Select_fixture_second_store,type=drop_down,price=1,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,option_title=Select Option 2_fixture_second_store|name=Test Field Title_fixture_second_store,type=field|name=Test Date and Time Title_fixture_second_store,price=8,type=date_time|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, +simple,base,,Default,simple,New Product,,,9,1,"Catalog, Search","base,secondwebsite",,10,,,,Taxable Goods,new-product,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1,sku=1-text,price=100|name=Test Date and Time Title,type=date_time,required=1,sku=2-date,price=200|name=Test Select,type=drop_down,required=1,sku=3-1-select,price=310,option_title=Select Option 1|name=Test Select,type=drop_down,required=1,sku=3-2-select,price=320,option_title=Select Option 2|name=Test Checkbox,type=checkbox,required=1,sku=4-1-select,price=410,option_title=Checkbox Option 1|name=Test Checkbox,type=checkbox,required=1,sku=4-2-select,price=420,option_title=Checkbox Option 2|name=Test Radio,type=radio,required=1,sku=5-1-radio,price=510,option_title=Radio Option 1|name=Test Radio,type=radio,required=1,sku=5-2-radio,price=520,option_title=Radio Option 2",,1,1,999,0,0,0,1,10000,1,1,0,0,,,,,,,,,,,Block after Info Column,,, +simple,,default,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_default,type=field,sku=1-text|name=Test Date and Time Title_default,type=date_time,sku=2-date|name=Test Select_default,type=drop_down,sku=3-1-select,option_title=Select Option 1_default|name=Test Select_default,type=drop_down,sku=3-2-select,option_title=Select Option 2_default|name=Test Checkbox_default,type=checkbox,sku=4-1-select,option_title=Checkbox Option 1_default|name=Test Checkbox_default,type=checkbox,sku=4-2-select,option_title=Checkbox Option 2_default|name=Test Radio_default,type=radio,sku=5-1-radio,option_title=Radio Option 1_default|name=Test Radio_default,type=radio,sku=5-2-radio,option_title=Radio Option 2_default",,,,,,,,,,,,,,,,,,,,,,,,,,, +simple,,secondstore,Default,simple,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title_fixture_second_store,type=field,sku=1-text,price=101|name=Test Date and Time Title_fixture_second_store,type=date_time,sku=2-date,price=201|name=Test Select_fixture_second_store,type=drop_down,sku=3-1-select,price=311,option_title=Select Option 1_fixture_second_store|name=Test Select_fixture_second_store,type=drop_down,sku=3-2-select,price=321,option_title=Select Option 2_fixture_second_store|name=Test Checkbox_second_store,type=checkbox,sku=4-1-select,price=411,option_title=Checkbox Option 1_second_store|name=Test Checkbox_second_store,type=checkbox,sku=4-2-select,price=421,option_title=Checkbox Option 2_second_store|name=Test Radio_fixture_second_store,type=radio,sku=5-1-radio,price=511,option_title=Radio Option 1_fixture_second_store|name=Test Radio_fixture_second_store,type=radio,sku=5-2-radio,price=521,option_title=Radio Option 2_fixture_second_store",,,,,,,,,,,,,,,,,,,,,,,,,,, From 40c0e2859b1bf4f5795a403861dd231b1e2483ae Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Wed, 28 Aug 2019 11:37:58 +0300 Subject: [PATCH 486/841] =?UTF-8?q?magento/magento2#:=20=E2=80=9CBad=20req?= =?UTF-8?q?uest=E2=80=9D=20is=20never=20shown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Customer/Controller/Account/Confirm.php | 31 +++++++++++-------- .../Unit/Controller/Account/ConfirmTest.php | 29 +++++++++-------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/app/code/Magento/Customer/Controller/Account/Confirm.php b/app/code/Magento/Customer/Controller/Account/Confirm.php index 2b3cb9aa61ab5..adca90c5e5f24 100644 --- a/app/code/Magento/Customer/Controller/Account/Confirm.php +++ b/app/code/Magento/Customer/Controller/Account/Confirm.php @@ -6,25 +6,27 @@ */ namespace Magento\Customer\Controller\Account; -use Magento\Customer\Model\Url; -use Magento\Framework\App\Action\Context; -use Magento\Customer\Model\Session; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Store\Model\StoreManagerInterface; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Controller\AbstractAccount; use Magento\Customer\Helper\Address; +use Magento\Customer\Model\Session; +use Magento\Customer\Model\Url; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Controller\ResultFactory; use Magento\Framework\UrlFactory; use Magento\Framework\Exception\StateException; use Magento\Store\Model\ScopeInterface; -use Magento\Framework\Controller\ResultFactory; +use Magento\Store\Model\StoreManagerInterface; /** * Class Confirm * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Confirm extends \Magento\Customer\Controller\AbstractAccount +class Confirm extends AbstractAccount implements HttpGetActionInterface { /** * @var \Magento\Framework\App\Config\ScopeConfigInterface @@ -147,13 +149,16 @@ public function execute() $resultRedirect->setPath('*/*/'); return $resultRedirect; } - try { - $customerId = $this->getRequest()->getParam('id', false); - $key = $this->getRequest()->getParam('key', false); - if (empty($customerId) || empty($key)) { - throw new \Exception(__('Bad request.')); - } + $customerId = $this->getRequest()->getParam('id', false); + $key = $this->getRequest()->getParam('key', false); + if (empty($customerId) || empty($key)) { + $this->messageManager->addErrorMessage(__('Bad request.')); + $url = $this->urlModel->getUrl('*/*/index', ['_secure' => true]); + return $resultRedirect->setUrl($this->_redirect->error($url)); + } + + try { // log in and send greeting email $customerEmail = $this->customerRepository->getById($customerId)->getEmail(); $customer = $this->customerAccountManagement->activate($customerEmail, $key); diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php index 01fc465d4ae84..a3be1e8f8830f 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php @@ -203,10 +203,9 @@ public function testNoCustomerIdInRequest($customerId, $key) ->with($this->equalTo('key'), false) ->will($this->returnValue($key)); - $exception = new \Exception('Bad request.'); - $this->messageManagerMock->expects($this->once()) - ->method('addException') - ->with($this->equalTo($exception), $this->equalTo('There was an error confirming the account')); + $this->messageManager->expects($this->once()) + ->method('addErrorMessage') + ->with(__('Bad request.')); $testUrl = 'http://example.com'; $this->urlMock->expects($this->once()) @@ -255,10 +254,12 @@ public function testSuccessMessage($customerId, $key, $vatValidationEnabled, $ad $this->requestMock->expects($this->any()) ->method('getParam') - ->willReturnMap([ - ['id', false, $customerId], - ['key', false, $key], - ]); + ->willReturnMap( + [ + ['id', false, $customerId], + ['key', false, $key], + ] + ); $this->customerRepositoryMock->expects($this->any()) ->method('getById') @@ -372,11 +373,13 @@ public function testSuccessRedirect( $this->requestMock->expects($this->any()) ->method('getParam') - ->willReturnMap([ - ['id', false, $customerId], - ['key', false, $key], - ['back_url', false, $backUrl], - ]); + ->willReturnMap( + [ + ['id', false, $customerId], + ['key', false, $key], + ['back_url', false, $backUrl], + ] + ); $this->customerRepositoryMock->expects($this->any()) ->method('getById') From 10ab3f200580bb601c7e6763fa957eaa64a62b46 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@users.noreply.github.com> Date: Wed, 28 Aug 2019 14:16:34 +0100 Subject: [PATCH 487/841] Update app/code/Magento/AuthorizenetCardinal/README.md Co-Authored-By: Jeff Matthews <matthews.jeffery@gmail.com> --- app/code/Magento/AuthorizenetCardinal/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/AuthorizenetCardinal/README.md b/app/code/Magento/AuthorizenetCardinal/README.md index 620dfbfc722de..0bd63130471bd 100644 --- a/app/code/Magento/AuthorizenetCardinal/README.md +++ b/app/code/Magento/AuthorizenetCardinal/README.md @@ -1,6 +1,6 @@ # Magento_AuthorizenetCardinal module -The Magento_AuthorizenetCardinal module provides a possibility to enable 3D Secure 2.0 support for AuthorizenetAcceptjs payment integration. +Use the Magento_AuthorizenetCardinal module to enable 3D Secure 2.0 support for AuthorizenetAcceptjs payment integrations. ## Structure From f7323b2a31473daac971b5e8136dc5c51cc2deac Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Wed, 28 Aug 2019 19:04:39 +0530 Subject: [PATCH 488/841] Correct spelling --- .../SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml b/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml index 2eed5ba6c548b..64918c24cdc61 100644 --- a/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml +++ b/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml @@ -64,7 +64,7 @@ function generateCouponCodes(idPrefix, generateUrl, grid) { try { response = JSON.parse(transport.responseText); } catch (e) { - console.warn('An error occured while parsing response'); + console.warn('An error occurred while parsing response'); } } if (couponCodesGrid) { From e5869014a8e7ea98cf3a6bb5a822c2683d2841a2 Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Wed, 28 Aug 2019 16:49:22 +0300 Subject: [PATCH 489/841] =?UTF-8?q?magento/magento2#:=20=E2=80=9CBad=20req?= =?UTF-8?q?uest=E2=80=9D=20is=20never=20shown?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Customer/Test/Unit/Controller/Account/ConfirmTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php index a3be1e8f8830f..28f897adf9176 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmTest.php @@ -203,7 +203,7 @@ public function testNoCustomerIdInRequest($customerId, $key) ->with($this->equalTo('key'), false) ->will($this->returnValue($key)); - $this->messageManager->expects($this->once()) + $this->messageManagerMock->expects($this->once()) ->method('addErrorMessage') ->with(__('Bad request.')); From 5a782f7d54ebaa992802122b09cc14b2aa11f441 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@users.noreply.github.com> Date: Wed, 28 Aug 2019 14:50:53 +0100 Subject: [PATCH 490/841] Update Magento_Authorizenet module description --- app/code/Magento/Authorizenet/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Authorizenet/README.md b/app/code/Magento/Authorizenet/README.md index 6d157860e42d6..62598837bee6d 100644 --- a/app/code/Magento/Authorizenet/README.md +++ b/app/code/Magento/Authorizenet/README.md @@ -1,6 +1,7 @@ # Magento_Authorizenet module -The Magento_Authorizenet module is a part of the staging functionality in Magento Commerce. The module adds the “Configurations” tab and the configuration wizard to the Schedule Update form of a product. You can change the Configurable Product attributes in campaigns. These updates are shown on the campaign dashboard. +The Magento_Authorizenet module implements the integration with the Authorize.Net payment gateway and makes the latter available as a payment method in Magento. + ## Extensibility Extension developers can interact with the Magento_Authorizenet module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). From 6971c763d544013902b6f12961ddf0e557718e1f Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Wed, 28 Aug 2019 10:07:19 -0500 Subject: [PATCH 491/841] MC-19618: Update schema for layered navigation output - review fixes --- .../Product/LayeredNavigation/Builder/Attribute.php | 2 +- .../Product/LayeredNavigation/Builder/Category.php | 2 +- .../Product/LayeredNavigation/Builder/Price.php | 2 +- .../{Builder => }/Formatter/LayerFormatter.php | 2 +- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) rename app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/{Builder => }/Formatter/LayerFormatter.php (97%) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php index 89df1be8d59f2..b70c9f6165fc6 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php @@ -12,7 +12,7 @@ use Magento\Framework\Api\Search\AggregationInterface; use Magento\Framework\Api\Search\AggregationValueInterface; use Magento\Framework\Api\Search\BucketInterface; -use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter\LayerFormatter; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Formatter\LayerFormatter; /** * @inheritdoc diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php index 97644328abc2a..cebafe31385ba 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php @@ -15,7 +15,7 @@ use Magento\Framework\Api\Search\AggregationValueInterface; use Magento\Framework\Api\Search\BucketInterface; use Magento\Framework\App\ResourceConnection; -use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter\LayerFormatter; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Formatter\LayerFormatter; /** * @inheritdoc diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php index e84a8f1b11078..02b638edbdce8 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Price.php @@ -10,7 +10,7 @@ use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\LayerBuilderInterface; use Magento\Framework\Api\Search\AggregationInterface; use Magento\Framework\Api\Search\BucketInterface; -use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter\LayerFormatter; +use Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Formatter\LayerFormatter; /** * @inheritdoc diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Formatter/LayerFormatter.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Formatter/LayerFormatter.php similarity index 97% rename from app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Formatter/LayerFormatter.php rename to app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Formatter/LayerFormatter.php index 72495f8ef8524..48a1265b10fc3 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Formatter/LayerFormatter.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Formatter/LayerFormatter.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Builder\Formatter; +namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation\Formatter; /** * Format Layered Navigation Items diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 7fe5374611a70..20bc5ef9949a6 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -391,10 +391,10 @@ type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characterist } type LayerFilter { - name: String @doc(description: "Layered navigation filter name.") - request_var: String @doc(description: "Request variable name for filter query.") - filter_items_count: Int @doc(description: "Count of filter items in filter group.") - filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items.") + name: String @doc(description: "Layered navigation filter name.") @deprecated(reason: "Use Aggregation.label instead") + request_var: String @doc(description: "Request variable name for filter query.") @deprecated(reason: "Use Aggregation.attribute_code instead") + filter_items_count: Int @doc(description: "Count of filter items in filter group.") @deprecated(reason: "Use Aggregation.count instead") + filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items.") @deprecated(reason: "Use Aggregation.options instead") } interface LayerFilterItemInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\LayerFilterItemTypeResolverComposite") { From 9553775b6294b9149a6b5bbe35ce4f2791e36073 Mon Sep 17 00:00:00 2001 From: Maksym Novik <maksym.novik@decimadigital.com> Date: Wed, 28 Aug 2019 18:51:43 +0300 Subject: [PATCH 492/841] GraphQl-220: Implement exception logging. Removed client errors logging. --- app/etc/graphql/di.xml | 22 ++++------- .../Framework/GraphQl/Query/ErrorHandler.php | 17 ++++---- .../GraphQl/Query/Resolver/LoggerFactory.php | 39 ------------------- .../Query/Resolver/LoggerFactoryInterface.php | 28 ------------- 4 files changed, 14 insertions(+), 92 deletions(-) delete mode 100644 lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php delete mode 100644 lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactoryInterface.php diff --git a/app/etc/graphql/di.xml b/app/etc/graphql/di.xml index 474266bb9eff0..4370c3b445f65 100644 --- a/app/etc/graphql/di.xml +++ b/app/etc/graphql/di.xml @@ -7,29 +7,21 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Framework\GraphQl\Query\ErrorHandlerInterface" type="Magento\Framework\GraphQl\Query\ErrorHandler"/> - <preference for="Magento\Framework\GraphQl\Query\Resolver\LoggerFactoryInterface" type="Magento\Framework\GraphQl\Query\Resolver\LoggerFactory"/> - <virtualType name="GraphQLClientLogger" type="Magento\Framework\Logger\Monolog"> + <virtualType name="GraphQlLogger" type="Magento\Framework\Logger\Monolog"> <arguments> <argument name="handlers" xsi:type="array"> - <item name="error" xsi:type="object">GraphQLClientErrorHandler</item> + <item name="error" xsi:type="object">GraphQlErrorHandler</item> </argument> </arguments> </virtualType> - <virtualType name="GraphQLClientErrorHandler" type="Magento\Framework\Logger\Handler\Base"> + <virtualType name="GraphQlErrorHandler" type="Magento\Framework\Logger\Handler\Base"> <arguments> - <argument name="fileName" xsi:type="const">Magento\Framework\GraphQl\Query\ErrorHandler::CLIENT_LOG_FILE</argument> + <argument name="fileName" xsi:type="string">var/log/graphql/exception.log</argument> </arguments> </virtualType> - <virtualType name="GraphQLServerLogger" type="Magento\Framework\Logger\Monolog"> + <type name="Magento\Framework\GraphQl\Query\ErrorHandler"> <arguments> - <argument name="handlers" xsi:type="array"> - <item name="error" xsi:type="object">GraphQLServerErrorHandler</item> - </argument> - </arguments> - </virtualType> - <virtualType name="GraphQLServerErrorHandler" type="Magento\Framework\Logger\Handler\Base"> - <arguments> - <argument name="fileName" xsi:type="const">Magento\Framework\GraphQl\Query\ErrorHandler::SERVER_LOG_FILE</argument> + <argument name="logger" xsi:type="object">GraphQlLogger</argument> </arguments> - </virtualType> + </type> </config> diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php index 874f714284936..2661034116f9d 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandler.php @@ -8,7 +8,7 @@ namespace Magento\Framework\GraphQl\Query; use GraphQL\Error\ClientAware; -use Magento\Framework\GraphQl\Query\Resolver\LoggerFactoryInterface; +use Psr\Log\LoggerInterface; /** * @inheritDoc @@ -17,21 +17,18 @@ */ class ErrorHandler implements ErrorHandlerInterface { - const SERVER_LOG_FILE = 'var/log/graphql/server/exception.log'; - const CLIENT_LOG_FILE = 'var/log/graphql/client/exception.log'; - /** - * @var LoggerFactoryInterface + * @var LoggerInterface */ - private $loggerFactory; + private $logger; /** - * @param LoggerFactoryInterface $loggerFactory + * @param LoggerInterface $logger */ public function __construct( - LoggerFactoryInterface $loggerFactory + LoggerInterface $logger ) { - $this->loggerFactory = $loggerFactory; + $this->logger = $logger; } /** @@ -41,7 +38,7 @@ public function handle(array $errors, callable $formatter): array { return array_map( function (ClientAware $error) use ($formatter) { - $this->loggerFactory->getLogger($error)->error($error); + $this->logger->error($error); return $formatter($error); }, diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php deleted file mode 100644 index 2b35b53981100..0000000000000 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactory.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQl\Query\Resolver; - -use GraphQL\Error\ClientAware; -use Magento\Framework\ObjectManagerInterface; -use Psr\Log\LoggerInterface; - -/** - * @inheritDoc - */ -class LoggerFactory implements LoggerFactoryInterface -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - public function __construct( - ObjectManagerInterface $objectManager - ) { - $this->objectManager = $objectManager; - } - - /** - * @inheritDoc - */ - public function getLogger(ClientAware $clientAware): LoggerInterface - { - return $clientAware->isClientSafe() ? - $this->objectManager->get('GraphQLClientLogger') : - $this->objectManager->get('GraphQLServerLogger'); - } -} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactoryInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactoryInterface.php deleted file mode 100644 index 778fb61db994d..0000000000000 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/LoggerFactoryInterface.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQl\Query\Resolver; - -use GraphQL\Error\ClientAware; -use Psr\Log\LoggerInterface; - -/** - * Resolve which logger to use for certain ClientAware exception - * - * @api - */ -interface LoggerFactoryInterface -{ - /** - * Get logger to use for certain ClientAware exception - * - * @param ClientAware $clientAware - * - * @return LoggerInterface - */ - public function getLogger(ClientAware $clientAware): LoggerInterface; -} From 6b3cdb6971eb2ee1f43c308a70a8587aaab42539 Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Tue, 27 Aug 2019 17:51:21 -0500 Subject: [PATCH 493/841] MC-19016: Date of Birth field defaulting to "1/1/1970" on customer form save - Fix birthday in locale date format --- .../Magento/Customer/Block/Widget/Dob.php | 57 +++-- .../Test/Unit/Block/Widget/DobTest.php | 212 +++++++++++------- 2 files changed, 172 insertions(+), 97 deletions(-) diff --git a/app/code/Magento/Customer/Block/Widget/Dob.php b/app/code/Magento/Customer/Block/Widget/Dob.php index 55101fb82afd0..d874729d9132e 100644 --- a/app/code/Magento/Customer/Block/Widget/Dob.php +++ b/app/code/Magento/Customer/Block/Widget/Dob.php @@ -99,11 +99,34 @@ public function isRequired() */ public function setDate($date) { - $this->setTime($date ? strtotime($date) : false); + $this->setTime($this->filterTime($date)); $this->setValue($this->applyOutputFilter($date)); return $this; } + /** + * Sanitizes time + * + * @param mixed $value + * @return bool|int + */ + private function filterTime($value) + { + $time = false; + if ($value) { + if ($value instanceof \DateTimeInterface) { + $time = $value->getTimestamp(); + } elseif (is_numeric($value)) { + $time = $value; + } elseif (is_string($value)) { + $time = strtotime($value); + $time = $time === false ? $this->_localeDate->date($value, null, false, false)->getTimestamp() : $time; + } + } + + return $time; + } + /** * Return Data Form Filter or false * @@ -200,21 +223,23 @@ public function getStoreLabel($attributeCode) */ public function getFieldHtml() { - $this->dateElement->setData([ - 'extra_params' => $this->getHtmlExtraParams(), - 'name' => $this->getHtmlId(), - 'id' => $this->getHtmlId(), - 'class' => $this->getHtmlClass(), - 'value' => $this->getValue(), - 'date_format' => $this->getDateFormat(), - 'image' => $this->getViewFileUrl('Magento_Theme::calendar.png'), - 'years_range' => '-120y:c+nn', - 'max_date' => '-1d', - 'change_month' => 'true', - 'change_year' => 'true', - 'show_on' => 'both', - 'first_day' => $this->getFirstDay() - ]); + $this->dateElement->setData( + [ + 'extra_params' => $this->getHtmlExtraParams(), + 'name' => $this->getHtmlId(), + 'id' => $this->getHtmlId(), + 'class' => $this->getHtmlClass(), + 'value' => $this->getValue(), + 'date_format' => $this->getDateFormat(), + 'image' => $this->getViewFileUrl('Magento_Theme::calendar.png'), + 'years_range' => '-120y:c+nn', + 'max_date' => '-1d', + 'change_month' => 'true', + 'change_year' => 'true', + 'show_on' => 'both', + 'first_day' => $this->getFirstDay() + ] + ); return $this->dateElement->getHtml(); } diff --git a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php index 6926eee4f28f2..8bfddac3cef8f 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php @@ -6,14 +6,31 @@ namespace Magento\Customer\Test\Unit\Block\Widget; +use Magento\Customer\Api\CustomerMetadataInterface; +use Magento\Customer\Api\Data\AttributeMetadataInterface; +use Magento\Customer\Api\Data\ValidationRuleInterface; +use Magento\Customer\Helper\Address; +use Magento\Framework\App\CacheInterface; +use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\Data\Form\FilterFactory; +use Magento\Framework\Escaper; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Customer\Block\Widget\Dob; use Magento\Framework\Locale\Resolver; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Framework\Stdlib\DateTime\Timezone; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Element\Html\Date; +use Magento\Framework\View\Element\Template\Context; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; +use Zend_Cache_Backend_BlackHole; +use Zend_Cache_Core; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class DobTest extends \PHPUnit\Framework\TestCase +class DobTest extends TestCase { /** Constants used in the unit tests */ const MIN_DATE = '01/01/2010'; @@ -43,82 +60,105 @@ class DobTest extends \PHPUnit\Framework\TestCase const YEAR_HTML = '<div><label for="year"><span>yy</span></label><input type="text" id="year" name="Year" value="14"></div>'; - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Api\Data\AttributeMetadataInterface */ + /** @var PHPUnit_Framework_MockObject_MockObject|AttributeMetadataInterface */ protected $attribute; /** @var Dob */ protected $_block; - /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Api\CustomerMetadataInterface */ + /** @var PHPUnit_Framework_MockObject_MockObject|CustomerMetadataInterface */ protected $customerMetadata; /** - * @var \Magento\Framework\Data\Form\FilterFactory|\PHPUnit_Framework_MockObject_MockObject + * @var FilterFactory|PHPUnit_Framework_MockObject_MockObject */ protected $filterFactory; /** - * @var \Magento\Framework\Escaper + * @var Escaper */ private $escaper; /** - * @var \Magento\Framework\View\Element\Template\Context + * @var Context */ private $context; + /** + * @var string + */ + private $_locale; + /** + * @inheritDoc + */ protected function setUp() { - $zendCacheCore = new \Zend_Cache_Core(); - $zendCacheCore->setBackend(new \Zend_Cache_Backend_BlackHole()); + $zendCacheCore = new Zend_Cache_Core(); + $zendCacheCore->setBackend(new Zend_Cache_Backend_BlackHole()); $frontendCache = $this->getMockForAbstractClass( - \Magento\Framework\Cache\FrontendInterface::class, + FrontendInterface::class, [], '', false ); $frontendCache->expects($this->any())->method('getLowLevelFrontend')->will($this->returnValue($zendCacheCore)); - $cache = $this->createMock(\Magento\Framework\App\CacheInterface::class); + $cache = $this->createMock(CacheInterface::class); $cache->expects($this->any())->method('getFrontend')->will($this->returnValue($frontendCache)); - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $localeResolver = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class); + $objectManager = new ObjectManager($this); + $localeResolver = $this->createMock(ResolverInterface::class); $localeResolver->expects($this->any()) ->method('getLocale') - ->willReturn(Resolver::DEFAULT_LOCALE); + ->willReturnCallback( + function () { + return $this->_locale; + } + ); $timezone = $objectManager->getObject( - \Magento\Framework\Stdlib\DateTime\Timezone::class, + Timezone::class, ['localeResolver' => $localeResolver] ); - $this->context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class); + $this->_locale = Resolver::DEFAULT_LOCALE; + $this->context = $this->createMock(Context::class); $this->context->expects($this->any())->method('getLocaleDate')->will($this->returnValue($timezone)); - $this->escaper = $this->getMockBuilder(\Magento\Framework\Escaper::class) + $this->escaper = $this->getMockBuilder(Escaper::class) ->disableOriginalConstructor() ->setMethods(['escapeHtml']) ->getMock(); $this->context->expects($this->any())->method('getEscaper')->will($this->returnValue($this->escaper)); - $this->attribute = $this->getMockBuilder(\Magento\Customer\Api\Data\AttributeMetadataInterface::class) + $this->attribute = $this->getMockBuilder(AttributeMetadataInterface::class) ->getMockForAbstractClass(); - $this->customerMetadata = $this->getMockBuilder(\Magento\Customer\Api\CustomerMetadataInterface::class) + $this->attribute + ->expects($this->any()) + ->method('getInputFilter') + ->willReturn('date'); + $this->customerMetadata = $this->getMockBuilder(CustomerMetadataInterface::class) ->getMockForAbstractClass(); $this->customerMetadata->expects($this->any()) ->method('getAttributeMetadata') ->will($this->returnValue($this->attribute)); - date_default_timezone_set('America/Los_Angeles'); - - $this->filterFactory = $this->getMockBuilder(\Magento\Framework\Data\Form\FilterFactory::class) - ->disableOriginalConstructor() - ->getMock(); + $this->filterFactory = $this->createMock(FilterFactory::class); + $this->filterFactory + ->expects($this->any()) + ->method('create') + ->willReturnCallback( + function () use ($timezone, $localeResolver) { + return new \Magento\Framework\Data\Form\Filter\Date( + $timezone->getDateFormatWithLongYear(), + $localeResolver + ); + } + ); - $this->_block = new \Magento\Customer\Block\Widget\Dob( + $this->_block = new Dob( $this->context, - $this->createMock(\Magento\Customer\Helper\Address::class), + $this->createMock(Address::class), $this->customerMetadata, - $this->createMock(\Magento\Framework\View\Element\Html\Date::class), + $this->createMock(Date::class), $this->filterFactory ); } @@ -143,17 +183,22 @@ public function isEnabledDataProvider() return [[true, true], [false, false]]; } + /** + * Tests isEnabled() + */ public function testIsEnabledWithException() { $this->customerMetadata->expects($this->any()) ->method('getAttributeMetadata') ->will( - $this->throwException(new NoSuchEntityException( - __( - 'No such entity with %fieldName = %fieldValue', - ['fieldName' => 'field', 'fieldValue' => 'value'] + $this->throwException( + new NoSuchEntityException( + __( + 'No such entity with %fieldName = %fieldValue', + ['fieldName' => 'field', 'fieldValue' => 'value'] + ) ) - )) + ) ); $this->assertSame(false, $this->_block->isEnabled()); } @@ -175,12 +220,14 @@ public function testIsRequiredWithException() $this->customerMetadata->expects($this->any()) ->method('getAttributeMetadata') ->will( - $this->throwException(new NoSuchEntityException( - __( - 'No such entity with %fieldName = %fieldValue', - ['fieldName' => 'field', 'fieldValue' => 'value'] + $this->throwException( + new NoSuchEntityException( + __( + 'No such entity with %fieldName = %fieldValue', + ['fieldName' => 'field', 'fieldValue' => 'value'] + ) ) - )) + ) ); $this->assertSame(false, $this->_block->isRequired()); } @@ -197,14 +244,15 @@ public function isRequiredDataProvider() * @param string|bool $date Date (e.g. '01/01/2020' or false for no date) * @param int|bool $expectedTime The value we expect from Dob::getTime() * @param string|bool $expectedDate The value we expect from Dob::getData('date') - * + * @param string $locale * @dataProvider setDateDataProvider */ - public function testSetDate($date, $expectedTime, $expectedDate) + public function testSetDate($date, $expectedTime, $expectedDate, $locale = Resolver::DEFAULT_LOCALE) { + $this->_locale = $locale; $this->assertSame($this->_block, $this->_block->setDate($date)); - $this->assertEquals($expectedTime, $this->_block->getTime()); - $this->assertEquals($expectedDate, $this->_block->getValue()); + $this->assertSame($expectedTime, $this->_block->getTime()); + $this->assertSame($expectedDate, $this->_block->getValue()); } /** @@ -212,32 +260,19 @@ public function testSetDate($date, $expectedTime, $expectedDate) */ public function setDateDataProvider() { - return [[self::DATE, strtotime(self::DATE), self::DATE], [false, false, false]]; - } - - public function testSetDateWithFilter() - { - $date = '2014-01-01'; - $filterCode = 'date'; - - $this->attribute->expects($this->once()) - ->method('getInputFilter') - ->willReturn($filterCode); - - $filterMock = $this->getMockBuilder(\Magento\Framework\Data\Form\Filter\Date::class) - ->disableOriginalConstructor() - ->getMock(); - $filterMock->expects($this->once()) - ->method('outputFilter') - ->with($date) - ->willReturn(self::DATE); - - $this->filterFactory->expects($this->once()) - ->method('create') - ->with($filterCode, ['format' => self::DATE_FORMAT]) - ->willReturn($filterMock); - - $this->_block->setDate($date); + return [ + [false, false, false], + ['', false, ''], + ['12/31/1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'], + ['31-12-1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'], + ['1999-12-31', strtotime('1999-12-31'), '12/31/1999', 'en_US'], + ['31 December 1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'], + ['12/31/1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'], + ['31-12-1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'], + ['31/12/1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'], + ['1999-12-31', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'], + ['31 Décembre 1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'], + ]; } /** @@ -301,7 +336,6 @@ public function getYearDataProvider() } /** - * The \Magento\Framework\Locale\ResolverInterface::DEFAULT_LOCALE * is used to derive the Locale that is used to determine the * value of Dob::getDateFormat() for that Locale. */ @@ -358,12 +392,12 @@ public function testGetMinDateRange($validationRules, $expectedValue) */ public function getMinDateRangeDataProvider() { - $emptyValidationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) + $emptyValidationRule = $this->getMockBuilder(ValidationRuleInterface::class) ->disableOriginalConstructor() ->setMethods(['getName', 'getValue']) ->getMockForAbstractClass(); - $validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) + $validationRule = $this->getMockBuilder(ValidationRuleInterface::class) ->disableOriginalConstructor() ->setMethods(['getName', 'getValue']) ->getMockForAbstractClass(); @@ -390,17 +424,22 @@ public function getMinDateRangeDataProvider() ]; } + /** + * Tests getMinDateRange() + */ public function testGetMinDateRangeWithException() { $this->customerMetadata->expects($this->any()) ->method('getAttributeMetadata') ->will( - $this->throwException(new NoSuchEntityException( - __( - 'No such entity with %fieldName = %fieldValue', - ['fieldName' => 'field', 'fieldValue' => 'value'] + $this->throwException( + new NoSuchEntityException( + __( + 'No such entity with %fieldName = %fieldValue', + ['fieldName' => 'field', 'fieldValue' => 'value'] + ) ) - )) + ) ); $this->assertNull($this->_block->getMinDateRange()); } @@ -424,12 +463,12 @@ public function testGetMaxDateRange($validationRules, $expectedValue) */ public function getMaxDateRangeDataProvider() { - $emptyValidationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) + $emptyValidationRule = $this->getMockBuilder(ValidationRuleInterface::class) ->disableOriginalConstructor() ->setMethods(['getName', 'getValue']) ->getMockForAbstractClass(); - $validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) + $validationRule = $this->getMockBuilder(ValidationRuleInterface::class) ->disableOriginalConstructor() ->setMethods(['getName', 'getValue']) ->getMockForAbstractClass(); @@ -455,21 +494,29 @@ public function getMaxDateRangeDataProvider() ]; } + /** + * Tests getMaxDateRange() + */ public function testGetMaxDateRangeWithException() { $this->customerMetadata->expects($this->any()) ->method('getAttributeMetadata') ->will( - $this->throwException(new NoSuchEntityException( - __( - 'No such entity with %fieldName = %fieldValue', - ['fieldName' => 'field', 'fieldValue' => 'value'] + $this->throwException( + new NoSuchEntityException( + __( + 'No such entity with %fieldName = %fieldValue', + ['fieldName' => 'field', 'fieldValue' => 'value'] + ) ) - )) + ) ); $this->assertNull($this->_block->getMaxDateRange()); } + /** + * Tests getHtmlExtraParams() without required options + */ public function testGetHtmlExtraParamsWithoutRequiredOption() { $this->escaper->expects($this->any()) @@ -487,6 +534,9 @@ public function testGetHtmlExtraParamsWithoutRequiredOption() ); } + /** + * Tests getHtmlExtraParams() with required options + */ public function testGetHtmlExtraParamsWithRequiredOption() { $this->attribute->expects($this->once()) From bcf6b57c346d79b2f3dcee3fdf2258c3757c4fa6 Mon Sep 17 00:00:00 2001 From: Dmytro Yushkin <dyushkin@adobe.com> Date: Wed, 28 Aug 2019 10:59:15 -0500 Subject: [PATCH 494/841] MC-19274: Bank Transfer Payment instructons are copied from default store view when order invoce is created --- .../BeforeOrderPaymentSaveObserver.php | 33 ++++--- .../BeforeOrderPaymentSaveObserverTest.php | 14 +-- .../Magento/OfflinePayments/composer.json | 3 +- .../Payment/Block/Info/Instructions.php | 12 +++ .../Test/Unit/Block/Info/InstructionsTest.php | 86 +++++++++++++++++-- .../templates/info/instructions.phtml | 2 +- .../templates/info/instructions.phtml | 2 +- 7 files changed, 124 insertions(+), 28 deletions(-) diff --git a/app/code/Magento/OfflinePayments/Observer/BeforeOrderPaymentSaveObserver.php b/app/code/Magento/OfflinePayments/Observer/BeforeOrderPaymentSaveObserver.php index b177b115d02ab..16ef7ecdf5011 100644 --- a/app/code/Magento/OfflinePayments/Observer/BeforeOrderPaymentSaveObserver.php +++ b/app/code/Magento/OfflinePayments/Observer/BeforeOrderPaymentSaveObserver.php @@ -3,38 +3,38 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); -/** - * OfflinePayments Observer - */ namespace Magento\OfflinePayments\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\OfflinePayments\Model\Banktransfer; use Magento\OfflinePayments\Model\Cashondelivery; use Magento\OfflinePayments\Model\Checkmo; +use Magento\Sales\Model\Order\Payment; +/** + * Sets payment additional information. + */ class BeforeOrderPaymentSaveObserver implements ObserverInterface { /** - * Sets current instructions for bank transfer account + * Sets current instructions for bank transfer account. * * @param \Magento\Framework\Event\Observer $observer * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ - public function execute(\Magento\Framework\Event\Observer $observer) + public function execute(\Magento\Framework\Event\Observer $observer): void { - /** @var \Magento\Sales\Model\Order\Payment $payment */ + /** @var Payment $payment */ $payment = $observer->getEvent()->getPayment(); $instructionMethods = [ Banktransfer::PAYMENT_METHOD_BANKTRANSFER_CODE, Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE ]; if (in_array($payment->getMethod(), $instructionMethods)) { - $payment->setAdditionalInformation( - 'instructions', - $payment->getMethodInstance()->getInstructions() - ); + $payment->setAdditionalInformation('instructions', $this->getInstructions($payment)); } elseif ($payment->getMethod() === Checkmo::PAYMENT_METHOD_CHECKMO_CODE) { $methodInstance = $payment->getMethodInstance(); if (!empty($methodInstance->getPayableTo())) { @@ -45,4 +45,17 @@ public function execute(\Magento\Framework\Event\Observer $observer) } } } + + /** + * Retrieve store-specific payment method instructions, or already saved if exists. + * + * @param Payment $payment + * @return string|null + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getInstructions(Payment $payment): ?string + { + return $payment->getAdditionalInformation('instructions') + ?: $payment->getMethodInstance()->getConfigData('instructions', $payment->getOrder()->getStoreId()); + } } diff --git a/app/code/Magento/OfflinePayments/Test/Unit/Observer/BeforeOrderPaymentSaveObserverTest.php b/app/code/Magento/OfflinePayments/Test/Unit/Observer/BeforeOrderPaymentSaveObserverTest.php index 51edaea0e659c..57c5ec533dc64 100644 --- a/app/code/Magento/OfflinePayments/Test/Unit/Observer/BeforeOrderPaymentSaveObserverTest.php +++ b/app/code/Magento/OfflinePayments/Test/Unit/Observer/BeforeOrderPaymentSaveObserverTest.php @@ -11,6 +11,7 @@ use Magento\OfflinePayments\Model\Banktransfer; use Magento\OfflinePayments\Model\Cashondelivery; use Magento\OfflinePayments\Observer\BeforeOrderPaymentSaveObserver; +use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Payment; use PHPUnit_Framework_MockObject_MockObject as MockObject; use Magento\OfflinePayments\Model\Checkmo; @@ -76,19 +77,12 @@ public function testBeforeOrderPaymentSaveWithInstructions($methodCode) $this->payment->expects(self::once()) ->method('getMethod') ->willReturn($methodCode); + $this->payment->method('getAdditionalInformation') + ->with('instructions') + ->willReturn('payment configuration'); $this->payment->expects(self::once()) ->method('setAdditionalInformation') ->with('instructions', 'payment configuration'); - $method = $this->getMockBuilder(Banktransfer::class) - ->disableOriginalConstructor() - ->getMock(); - - $method->expects(self::once()) - ->method('getInstructions') - ->willReturn('payment configuration'); - $this->payment->expects(self::once()) - ->method('getMethodInstance') - ->willReturn($method); $this->_model->execute($this->observer); } diff --git a/app/code/Magento/OfflinePayments/composer.json b/app/code/Magento/OfflinePayments/composer.json index 4de112ac72152..7bf4b78628a70 100644 --- a/app/code/Magento/OfflinePayments/composer.json +++ b/app/code/Magento/OfflinePayments/composer.json @@ -8,7 +8,8 @@ "php": "~7.1.3||~7.2.0||~7.3.0", "magento/framework": "*", "magento/module-checkout": "*", - "magento/module-payment": "*" + "magento/module-payment": "*", + "magento/module-sales": "*" }, "suggest": { "magento/module-config": "*" diff --git a/app/code/Magento/Payment/Block/Info/Instructions.php b/app/code/Magento/Payment/Block/Info/Instructions.php index 687c6b54a2f4f..f670fa6925bfb 100644 --- a/app/code/Magento/Payment/Block/Info/Instructions.php +++ b/app/code/Magento/Payment/Block/Info/Instructions.php @@ -25,6 +25,18 @@ class Instructions extends \Magento\Payment\Block\Info */ protected $_template = 'Magento_Payment::info/instructions.phtml'; + /** + * Gets payment method title for appropriate store. + * + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function getTitle() + { + return $this->getInfo()->getAdditionalInformation('method_title') + ?: $this->getMethod()->getConfigData('title', $this->getInfo()->getOrder()->getStoreId()); + } + /** * Get instructions text from order payment * (or from config, if instructions are missed in payment) diff --git a/app/code/Magento/Payment/Test/Unit/Block/Info/InstructionsTest.php b/app/code/Magento/Payment/Test/Unit/Block/Info/InstructionsTest.php index 68c76d94e02ae..88144b6e05c62 100644 --- a/app/code/Magento/Payment/Test/Unit/Block/Info/InstructionsTest.php +++ b/app/code/Magento/Payment/Test/Unit/Block/Info/InstructionsTest.php @@ -4,11 +4,12 @@ * See COPYING.txt for license details. */ -/** - * Test class for \Magento\Payment\Block\Info\Instructions - */ namespace Magento\Payment\Test\Unit\Block\Info; +use Magento\Payment\Model\MethodInterface; +use Magento\Sales\Model\Order; +use PHPUnit\Framework\MockObject\MockObject; + class InstructionsTest extends \PHPUnit\Framework\TestCase { /** @@ -25,10 +26,59 @@ protected function setUp() { $context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class); $this->_instructions = new \Magento\Payment\Block\Info\Instructions($context); - $this->_info = $this->createMock(\Magento\Payment\Model\Info::class); + $this->_info = $this->getMockBuilder(\Magento\Payment\Model\Info::class) + ->setMethods( + [ + 'getOrder', + 'getAdditionalInformation', + 'getMethodInstance' + ] + ) + ->disableOriginalConstructor() + ->getMock(); $this->_instructions->setData('info', $this->_info); } + /** + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testGetTitleFromPaymentAdditionalData() + { + $this->_info->method('getAdditionalInformation') + ->with('method_title') + ->willReturn('payment_method_title'); + + $this->getMethod()->expects($this->never()) + ->method('getConfigData'); + + $this->assertEquals($this->_instructions->getTitle(), 'payment_method_title'); + } + + /** + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testGetTitleFromPaymentMethodConfig() + { + $this->_info->method('getAdditionalInformation') + ->with('method_title') + ->willReturn(null); + + $this->getMethod()->expects($this->once()) + ->method('getConfigData') + ->with('title', null) + ->willReturn('payment_method_title'); + + $order = $this->getOrder(); + $this->_info->method('getOrder')->willReturn($order); + + $this->assertEquals($this->_instructions->getTitle(), 'payment_method_title'); + } + + /** + * @return void + */ public function testGetInstructionAdditionalInformation() { $this->_info->expects($this->once()) @@ -41,10 +91,13 @@ public function testGetInstructionAdditionalInformation() $this->assertEquals('get the instruction here', $this->_instructions->getInstructions()); } + /** + * @return void + */ public function testGetInstruction() { $methodInstance = $this->getMockBuilder( - \Magento\Payment\Model\MethodInterface::class + MethodInterface::class )->getMockForAbstractClass(); $methodInstance->expects($this->once()) ->method('getConfigData') @@ -59,4 +112,27 @@ public function testGetInstruction() ->willReturn($methodInstance); $this->assertEquals('get the instruction here', $this->_instructions->getInstructions()); } + + /** + * @return MethodInterface|MockObject + */ + private function getMethod() + { + $method = $this->getMockBuilder(MethodInterface::class) + ->getMockForAbstractClass(); + $this->_info->method('getMethodInstance') + ->willReturn($method); + + return $method; + } + + /** + * @return Order|MockObject + */ + private function getOrder() + { + return $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + } } diff --git a/app/code/Magento/Payment/view/adminhtml/templates/info/instructions.phtml b/app/code/Magento/Payment/view/adminhtml/templates/info/instructions.phtml index f60c1d063addf..904f0bd2e2cdc 100644 --- a/app/code/Magento/Payment/view/adminhtml/templates/info/instructions.phtml +++ b/app/code/Magento/Payment/view/adminhtml/templates/info/instructions.phtml @@ -9,7 +9,7 @@ * @see \Magento\Payment\Block\Info */ ?> -<p><?= $block->escapeHtml($block->getMethod()->getTitle()) ?></p> +<p><?= $block->escapeHtml($block->getTitle()) ?></p> <?php if ($block->getInstructions()) : ?> <table> <tbody> diff --git a/app/code/Magento/Payment/view/frontend/templates/info/instructions.phtml b/app/code/Magento/Payment/view/frontend/templates/info/instructions.phtml index 60efae16b1711..a8d2b15c3ea31 100644 --- a/app/code/Magento/Payment/view/frontend/templates/info/instructions.phtml +++ b/app/code/Magento/Payment/view/frontend/templates/info/instructions.phtml @@ -10,7 +10,7 @@ */ ?> <dl class="payment-method"> - <dt class="title"><?= $block->escapeHtml($block->getMethod()->getTitle()) ?></dt> + <dt class="title"><?= $block->escapeHtml($block->getTitle()) ?></dt> <?php if ($block->getInstructions()) : ?> <dd class="content"><?= /* @noEscape */ nl2br($block->escapeHtml($block->getInstructions())) ?></dd> <?php endif; ?> From 08de5cfce0fb0a27f41d6991b3fdc16a3840b6b2 Mon Sep 17 00:00:00 2001 From: Prabhu Ram <pganapat@adobe.com> Date: Wed, 28 Aug 2019 11:29:41 -0500 Subject: [PATCH 495/841] MC-19618: Update schema for layered navigation output - review fixes --- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 20bc5ef9949a6..d29a24428ae12 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -404,14 +404,14 @@ interface LayerFilterItemInterface @typeResolver(class: "Magento\\CatalogGraphQl } type Aggregation { - count: Int @doc(description: "Count of filter items in filter group.") - label: String @doc(description: "Layered navigation filter name.") + count: Int @doc(description: "The number of filter items in the filter group.") + label: String @doc(description: "The filter named displayed in layered navigation.") attribute_code: String! @doc(description: "Attribute code of the filter item.") - options: [AggregationOption] @doc(description: "Array of aggregation options.") + options: [AggregationOption] @doc(description: "Describes each aggregated filter option.") } type AggregationOption { - count: Int @doc(description: "Count of items by filter.") + count: Int @doc(description: "The number of items returned by the filter.") label: String! @doc(description: "Filter label.") value: String! @doc(description: "Value for filter request variable to be used in query.") } From 8c4bcf6721aba9963875ce7f497e618948ff1c78 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Wed, 28 Aug 2019 11:54:45 -0500 Subject: [PATCH 496/841] MC-19432: REST API returns 404 error instead of 400 --- app/code/Magento/Quote/Model/Quote/Item/Repository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Quote/Model/Quote/Item/Repository.php b/app/code/Magento/Quote/Model/Quote/Item/Repository.php index 2b4852251bca2..6fb512a619de4 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Repository.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Repository.php @@ -95,7 +95,7 @@ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem) $cartId = $cartItem->getQuoteId(); if (!$cartId) { throw new InputException( - __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'cartId']) + __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'quoteId']) ); } From 8a2fbffbad2da4c9b00e84cfc40d2d59c846379d Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 28 Aug 2019 12:14:33 -0500 Subject: [PATCH 497/841] MC-19702: Add input_type to customAttributeMetadata query --- .../Resolver/CustomAttributeMetadata.php | 14 ++++- .../Model/Resolver/Query/FrontendType.php | 61 +++++++++++++++++++ .../Magento/EavGraphQl/etc/schema.graphqls | 1 + 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/EavGraphQl/Model/Resolver/Query/FrontendType.php diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/CustomAttributeMetadata.php b/app/code/Magento/EavGraphQl/Model/Resolver/CustomAttributeMetadata.php index 62e3f01836619..85445580bb1fb 100644 --- a/app/code/Magento/EavGraphQl/Model/Resolver/CustomAttributeMetadata.php +++ b/app/code/Magento/EavGraphQl/Model/Resolver/CustomAttributeMetadata.php @@ -9,6 +9,7 @@ use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\EavGraphQl\Model\Resolver\Query\Type; +use Magento\EavGraphQl\Model\Resolver\Query\FrontendType; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; @@ -26,12 +27,19 @@ class CustomAttributeMetadata implements ResolverInterface */ private $type; + /** + * @var FrontendType + */ + private $frontendType; + /** * @param Type $type + * @param FrontendType $frontendType */ - public function __construct(Type $type) + public function __construct(Type $type, FrontendType $frontendType) { $this->type = $type; + $this->frontendType = $frontendType; } /** @@ -52,6 +60,7 @@ public function resolve( continue; } try { + $frontendType = $this->frontendType->getType($attribute['attribute_code'], $attribute['entity_type']); $type = $this->type->getType($attribute['attribute_code'], $attribute['entity_type']); } catch (InputException $exception) { $attributes['items'][] = new GraphQlNoSuchEntityException( @@ -78,7 +87,8 @@ public function resolve( $attributes['items'][] = [ 'attribute_code' => $attribute['attribute_code'], 'entity_type' => $attribute['entity_type'], - 'attribute_type' => ucfirst($type) + 'attribute_type' => ucfirst($type), + 'input_type' => $frontendType ]; } diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/Query/FrontendType.php b/app/code/Magento/EavGraphQl/Model/Resolver/Query/FrontendType.php new file mode 100644 index 0000000000000..c76f19e6dfeb4 --- /dev/null +++ b/app/code/Magento/EavGraphQl/Model/Resolver/Query/FrontendType.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\EavGraphQl\Model\Resolver\Query; + +use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Webapi\ServiceTypeToEntityTypeMap; + +/** + * Get frontend input type for EAV attribute + */ +class FrontendType +{ + /** + * @var AttributeRepositoryInterface + */ + private $attributeRepository; + + /** + * @var ServiceTypeToEntityTypeMap + */ + private $serviceTypeMap; + + /** + * @param AttributeRepositoryInterface $attributeRepository + * @param ServiceTypeToEntityTypeMap $serviceTypeMap + */ + public function __construct( + AttributeRepositoryInterface $attributeRepository, + ServiceTypeToEntityTypeMap $serviceTypeMap + ) { + $this->attributeRepository = $attributeRepository; + $this->serviceTypeMap = $serviceTypeMap; + } + + /** + * Return frontend type for attribute + * + * @param string $attributeCode + * @param string $entityType + * @return null|string + */ + public function getType(string $attributeCode, string $entityType): ?string + { + $mappedEntityType = $this->serviceTypeMap->getEntityType($entityType); + if ($mappedEntityType) { + $entityType = $mappedEntityType; + } + try { + $attribute = $this->attributeRepository->get($entityType, $attributeCode); + } catch (NoSuchEntityException $e) { + return null; + } + return $attribute->getFrontendInput(); + } +} diff --git a/app/code/Magento/EavGraphQl/etc/schema.graphqls b/app/code/Magento/EavGraphQl/etc/schema.graphqls index 0b174fbc4d84d..21aa7001fab2b 100644 --- a/app/code/Magento/EavGraphQl/etc/schema.graphqls +++ b/app/code/Magento/EavGraphQl/etc/schema.graphqls @@ -13,6 +13,7 @@ type Attribute @doc(description: "Attribute contains the attribute_type of the s attribute_code: String @doc(description: "The unique identifier for an attribute code. This value should be in lowercase letters without spaces.") entity_type: String @doc(description: "The type of entity that defines the attribute") attribute_type: String @doc(description: "The data type of the attribute") + input_type: String @doc(description: "The frontend input type of the attribute") attribute_options: [AttributeOption] @resolver(class: "Magento\\EavGraphQl\\Model\\Resolver\\AttributeOptions") @doc(description: "Attribute options list.") } From bd5a0122f9b7bc20f8e236f030b2ca2ced01adb2 Mon Sep 17 00:00:00 2001 From: shankar <konar.shankar2013@gmail.com> Date: Wed, 28 Aug 2019 22:48:52 +0530 Subject: [PATCH 498/841] Fixed static build test --- app/code/Magento/Review/Block/Adminhtml/Add.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/code/Magento/Review/Block/Adminhtml/Add.php b/app/code/Magento/Review/Block/Adminhtml/Add.php index 27d1e28c2e478..14392220a3db4 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Add.php +++ b/app/code/Magento/Review/Block/Adminhtml/Add.php @@ -21,17 +21,13 @@ class Add extends \Magento\Backend\Block\Widget\Form\Container protected function _construct() { parent::_construct(); - $this->_blockGroup = 'Magento_Review'; $this->_controller = 'adminhtml'; $this->_mode = 'add'; - $this->buttonList->update('save', 'label', __('Save Review')); $this->buttonList->update('save', 'id', 'save_button'); - $this->buttonList->update('reset', 'id', 'reset_button'); $this->buttonList->update('reset', 'onclick', 'window.review.formReset()'); - $this->_formScripts[] = ' require(["prototype"], function(){ toggleParentVis("add_review_form"); @@ -39,7 +35,6 @@ protected function _construct() toggleVis("reset_button"); }); '; - // @codingStandardsIgnoreStart $this->_formInitScripts[] = ' require(["jquery","prototype"], function(jQuery){ From f6cffdc823cb67d875bc458806587459f3d3d4ce Mon Sep 17 00:00:00 2001 From: Dmytro Poperechnyy <dpoperechnyy@magento.com> Date: Wed, 28 Aug 2019 12:24:16 -0500 Subject: [PATCH 499/841] MC-19246: Fix missing shim configurations - Remove redundant dependency; --- lib/web/jquery/patches/jquery-ui.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web/jquery/patches/jquery-ui.js b/lib/web/jquery/patches/jquery-ui.js index 64465430cc584..cb67fab8030b4 100644 --- a/lib/web/jquery/patches/jquery-ui.js +++ b/lib/web/jquery/patches/jquery-ui.js @@ -5,8 +5,8 @@ define([ 'jquery', - 'jquery-ui-modules/widget', - 'jquery-ui-modules/dialog' + 'jquery-ui-modules/widget' + // 'jquery-ui-modules/dialog' - do not enable this dependency because this is already a mixin for the dialog ui component ], function ($) { 'use strict'; From 53ffcd761360d5d2fc3840e3227b744d8e3b318b Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 28 Aug 2019 14:38:21 -0500 Subject: [PATCH 500/841] MC-17627: Dependency static test does not analyze content of phtml files - Fix handling of comment sanitation - Fix incorrect urls - Added checking route by frontName --- .../catalog/product/edit/super/config.phtml | 2 +- .../TestFramework/Dependency/PhpRule.php | 31 ++++++++++++++----- .../Dependency/Route/RouteMapper.php | 9 ++++++ .../Magento/Test/Integrity/DependencyTest.php | 26 +++++++--------- .../dependency_test/whitelist/routes_ce.php | 2 +- 5 files changed, 45 insertions(+), 25 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml index c11a1adc19896..240c5e65c79c3 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/edit/super/config.phtml @@ -64,7 +64,7 @@ "productsProvider": "configurable_associated_product_listing.data_source", "productsMassAction": "configurable_associated_product_listing.configurable_associated_product_listing.product_columns.ids", "productsColumns": "configurable_associated_product_listing.configurable_associated_product_listing.product_columns", - "productsGridUrl": "<?= /* @noEscape */ $block->getUrl('catalog/product/associated_grid', ['componentJson' => true]) ?>", + "productsGridUrl": "<?= /* @noEscape */ $block->getUrl('catalog/product_associated/grid', ['componentJson' => true]) ?>", "configurableVariations": "configurableVariations" } } diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php index 4958795412681..990df68a95cf3 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php @@ -288,8 +288,8 @@ private function isPluginDependency($dependent, $dependency) */ protected function _caseGetUrl(string $currentModule, string &$contents): array { - $pattern = '#(\->|:)(?<source>getUrl\(([\'"])(?<route_id>[a-z0-9\-_]{3,})' - .'(/(?<controller_name>[a-z0-9\-_]+))?(/(?<action_name>[a-z0-9\-_]+))?\3)#i'; + $pattern = '#(\->|:)(?<source>getUrl\(([\'"])(?<route_id>[a-z0-9\-_]{3,}|\*)' + .'(/(?<controller_name>[a-z0-9\-_]+|\*))?(/(?<action_name>[a-z0-9\-_]+))?\3|\*)#i'; $dependencies = []; if (!preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER)) { @@ -298,10 +298,27 @@ protected function _caseGetUrl(string $currentModule, string &$contents): array try { foreach ($matches as $item) { + $routeId = $item['route_id']; + $controllerName = $item['controller_name'] ?? UrlInterface::DEFAULT_CONTROLLER_NAME; + $actionName = $item['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME; + if ( + in_array( + implode('/', [$routeId, $controllerName, $actionName]), + $this->getRoutesWhitelist())) { + continue; + } + // skip rest + if($routeId == "rest") { //MC-17627 + continue; + } + // skip wildcards + if($routeId == "*" || $controllerName == "*" || $actionName == "*" ) { //MC-17627 + continue; + } $modules = $this->routeMapper->getDependencyByRoutePath( - $item['route_id'], - $item['controller_name'] ?? UrlInterface::DEFAULT_CONTROLLER_NAME, - $item['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME + $routeId, + $controllerName, + $actionName ); if (!in_array($currentModule, $modules)) { if (count($modules) === 1) { @@ -315,9 +332,7 @@ protected function _caseGetUrl(string $currentModule, string &$contents): array } } } catch (NoSuchActionException $e) { - if (array_search($e->getMessage(), $this->getRoutesWhitelist()) === false) { - throw new LocalizedException(__('Invalid URL path: %1', $e->getMessage()), $e); - } + throw new LocalizedException(__('Invalid URL path: %1', $e->getMessage()), $e); } return $dependencies; diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php index 87cc0985a053b..a3d6fbffa8c7e 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php @@ -243,6 +243,15 @@ private function processConfigFile(string $module, string $configFile) if (!in_array($module, $this->routers[$routerId][$routeId])) { $this->routers[$routerId][$routeId][] = $module; } + if(isset($route['frontName'])) { + $frontName = (string)$route['frontName']; + if (!isset($this->routers[$routerId][$frontName])) { + $this->routers[$routerId][$frontName] = []; + } + if (!in_array($module, $this->routers[$routerId][$frontName])) { + $this->routers[$routerId][$frontName][] = $module; + } + } } } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index 540fcc76349d1..e52c31723725b 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -284,21 +284,20 @@ private static function getRoutesWhitelist(): array */ protected function _getCleanedFileContents($fileType, $file) { - $contents = (string)file_get_contents($file); switch ($fileType) { case 'php': - //Removing php comments - $contents = preg_replace('~/\*.*?\*/~m', '', $contents); - $contents = preg_replace('~^\s*/\*.*?\*/~sm', '', $contents); - $contents = preg_replace('~^\s*//.*$~m', '', $contents); - break; + return php_strip_whitespace($file); case 'layout': case 'config': //Removing xml comments - $contents = preg_replace('~\<!\-\-/.*?\-\-\>~s', '', $contents); + return preg_replace( + '~\<!\-\-/.*?\-\-\>~s', + '', + (string)file_get_contents($file) + ); break; case 'template': - //Removing html + $contents = php_strip_whitespace($file); $contentsWithoutHtml = ''; preg_replace_callback( '~(<\?(php|=)\s+.*\?>)~sU', @@ -308,15 +307,13 @@ function ($matches) use ($contents, &$contentsWithoutHtml) { }, $contents ); - $contents = $contentsWithoutHtml; - //Removing php comments - $contents = preg_replace('~/\*.*?\*/~s', '', $contents); - $contents = preg_replace('~^\s*//.*$~s', '', $contents); - break; } - return $contents; + + return (string)file_get_contents($file); } + + /** * @inheritdoc * @throws \Exception @@ -395,7 +392,6 @@ protected function getDependenciesFromFiles($module, $fileType, $file, $contents $newDependencies = $rule->getDependencyInfo($module, $fileType, $file, $contents); $dependencies = array_merge($dependencies, $newDependencies); } - foreach ($dependencies as $key => $dependency) { foreach (self::$whiteList as $namespace) { if (strpos($dependency['source'], $namespace) !== false) { diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php index 9ebc951a3a3a1..1aef3ffdf104a 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php @@ -6,5 +6,5 @@ declare(strict_types=1); return [ - 'privacy-policy-cookie-restriction-mode/index/index', + 'privacy-policy-cookie-restriction-mode/index/index' ]; From efd076e6f9417ee1799c69383d8d914f9c40d240 Mon Sep 17 00:00:00 2001 From: Krissy Hiserote <khiserote@magento.com> Date: Wed, 28 Aug 2019 15:08:41 -0500 Subject: [PATCH 501/841] MC-16650: Product Attribute Type Price Not Displaying - fix test failures --- .../Test/Unit/Model/Layer/FilterListTest.php | 10 ++++-- .../LayerNavigationOfCatalogSearchTest.xml | 2 +- .../attribute_special_price_filterable.php | 2 +- .../_files/multiple_visible_products.php | 32 ++++++++++++++++--- .../multiple_visible_products_rollback.php | 7 ++++ .../Model/Layer/Filter/DecimalTest.php | 2 +- 6 files changed, 45 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php index 6943bc27fd77c..731c5efd99746 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/FilterListTest.php @@ -73,9 +73,13 @@ public function testGetFilters($method, $value, $expectedClass) $this->objectManagerMock->expects($this->at(1)) ->method('create') - ->with($expectedClass, [ - 'data' => ['attribute_model' => $this->attributeMock], - 'layer' => $this->layerMock]) + ->with( + $expectedClass, + [ + 'data' => ['attribute_model' => $this->attributeMock], + 'layer' => $this->layerMock + ] + ) ->will($this->returnValue('filter')); $this->attributeMock->expects($this->once()) diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml index e91fb9b7a55cc..210b474af2e02 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/LayerNavigationOfCatalogSearchTest.xml @@ -14,7 +14,7 @@ <title value="Layer Navigation of Catalog Search Should Equalize Price Range As Default Configuration"/> <description value="Make sure filter of custom attribute with type of price displays on storefront Catalog page and price range should respect the configuration in Admin site"/> <testCaseId value="MC-16979"/> - <issueId value="MC-16650"/> + <useCaseId value="MC-16650"/> <severity value="MAJOR"/> <group value="CatalogSearch"/> </annotations> diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php index 3c92db99cd4f1..eedff7aaefb1d 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php @@ -9,4 +9,4 @@ \Magento\Catalog\Setup\CategorySetup::class ); -$installer->updateAttribute('catalog_product', 'special_price', 'is_filterable', 1); \ No newline at end of file +$installer->updateAttribute('catalog_product', 'special_price', 'is_filterable', 1); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php index 4ace41d23c872..f5c22c570911f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php @@ -4,6 +4,30 @@ * See COPYING.txt for license details. */ +$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); +$category->isObjectNew(true); +$category->setId( + 100 +)->setCreatedAt( + '2014-06-23 09:50:07' +)->setName( + 'Category 100' +)->setParentId( + 2 +)->setPath( + '1/2/100' +)->setLevel( + 2 +)->setAvailableSortBy( + ['position', 'name'] +)->setDefaultSortBy( + 'name' +)->setIsActive( + true +)->setPosition( + 1 +)->save(); + /** @var $product \Magento\Catalog\Model\Product */ $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); $product->isObjectNew(true); @@ -25,7 +49,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCategoryIds([2]) + ->setCategoryIds([100]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('10') ->save(); @@ -50,7 +74,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCategoryIds([2]) + ->setCategoryIds([100]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('20') ->save(); @@ -70,7 +94,7 @@ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) - ->setCategoryIds([2]) + ->setCategoryIds([100]) ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) ->setSpecialPrice('30') - ->save(); + ->save(); \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php index a9155d3fadf0b..76876c39b7d72 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php @@ -12,6 +12,13 @@ $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); +/** @var $category \Magento\Catalog\Model\Category */ +$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); +$category->load(100); +if ($category->getId()) { + $category->delete(); +} + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php index 33dea3ea37179..63973ac0fbe20 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php @@ -61,7 +61,7 @@ public function testApplyProductCollection() /** @var $objectManager \Magento\TestFramework\ObjectManager */ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $category = $objectManager->create(\Magento\Catalog\Model\Category::class); - $category->load(2); + $category->load(100); $this->_model->getLayer()->setCurrentCategory($category); /** @var $attribute \Magento\Catalog\Model\Entity\Attribute */ From 9cecea1826cd3eaf45b09cfd462bc8d013d47a71 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sergey.dovbenko@gmail.com> Date: Wed, 28 Aug 2019 23:53:07 +0300 Subject: [PATCH 502/841] Update app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php Co-Authored-By: Lena Orobei <oorobei@magento.com> --- .../Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index 8997ff06b5c6d..86ba3df0eafed 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -13,7 +13,6 @@ use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Store\Model\ScopeInterface; use Magento\Newsletter\Model\Config; /** From 68940ec051915c416c89cccd0e57df857d6d0fee Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sergey.dovbenko@gmail.com> Date: Wed, 28 Aug 2019 23:55:20 +0300 Subject: [PATCH 503/841] Renamed $config to $newsletterConfig Co-Authored-By: Lena Orobei <oorobei@magento.com> --- .../Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index 86ba3df0eafed..8133aae93f5a9 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -33,7 +33,7 @@ class CreateCustomer implements ResolverInterface /** * @var Config */ - private $config; + private $newsletterConfig; /** * CreateCustomer constructor. From b88a3db488a1ea083da7e9db363550ff6363f444 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sergey.dovbenko@gmail.com> Date: Wed, 28 Aug 2019 23:56:48 +0300 Subject: [PATCH 504/841] Renamed config to newsletterConfig Co-Authored-By: Lena Orobei <oorobei@magento.com> --- .../Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index 8133aae93f5a9..8a6163494d822 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -45,7 +45,7 @@ class CreateCustomer implements ResolverInterface public function __construct( ExtractCustomerData $extractCustomerData, CreateCustomerAccount $createCustomerAccount, - Config $config + Config $newsletterConfig ) { $this->config = $config; $this->extractCustomerData = $extractCustomerData; From 010536d6b152d8826e2c6c1b60e8c38e4f59f6a4 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sergey.dovbenko@gmail.com> Date: Wed, 28 Aug 2019 23:57:31 +0300 Subject: [PATCH 505/841] Renamed config to newsletterConfig Co-Authored-By: Lena Orobei <oorobei@magento.com> --- .../Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index 8a6163494d822..caf1436ae3eee 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -47,7 +47,7 @@ public function __construct( CreateCustomerAccount $createCustomerAccount, Config $newsletterConfig ) { - $this->config = $config; + $this->newsLetterConfig = $newsLetterConfig; $this->extractCustomerData = $extractCustomerData; $this->createCustomerAccount = $createCustomerAccount; } From bf5d62286f1c88b71f7837ca24315c0e032c3fd5 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sergey.dovbenko@gmail.com> Date: Wed, 28 Aug 2019 23:57:48 +0300 Subject: [PATCH 506/841] Renamed config to newsletterConfig Co-Authored-By: Lena Orobei <oorobei@magento.com> --- .../Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index caf1436ae3eee..b7ac4d5ea5fdd 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -66,7 +66,7 @@ public function resolve( throw new GraphQlInputException(__('"input" value should be specified')); } - if (!$this->config->isActive()) { + if (!$this->newsLetterConfig->isActive()) { $args['input']['is_subscribed'] = false; } From 75e240865563979af8c9876e8530cb3818d51ed7 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sergey.dovbenko@gmail.com> Date: Wed, 28 Aug 2019 23:58:21 +0300 Subject: [PATCH 507/841] Deleted empty line Co-Authored-By: Lena Orobei <oorobei@magento.com> --- .../Test/Unit/Observer/PredispatchNewsletterObserverTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php index d0cfa507eb42f..6846231319d69 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php @@ -93,7 +93,6 @@ public function testNewsletterEnabled() : void ->method('isActive') ->with(ScopeInterface::SCOPE_STORE) ->willReturn(true); - $observerMock->expects($this->never()) ->method('getData') ->with('controller_action') From c0caee9c5d36323979ec35aa46cc7ef36eccdbd6 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sergey.dovbenko@gmail.com> Date: Wed, 28 Aug 2019 23:58:49 +0300 Subject: [PATCH 508/841] Removed Exception Co-Authored-By: Lena Orobei <oorobei@magento.com> --- .../testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index 9654be4f5e3cc..c5714012f38c9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -277,7 +277,6 @@ public function testCreateCustomerIfNameEmpty() /** * @magentoConfigFixture default_store newsletter/general/active 0 - * @throws \Exception */ public function testCreateCustomerSubscribed() { From a2a2e16e000eed074d7219a2519cab9a5d740a95 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 28 Aug 2019 16:20:14 -0500 Subject: [PATCH 509/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed depedencies of modules --- app/code/Magento/Braintree/composer.json | 6 +++--- app/code/Magento/Catalog/composer.json | 3 +-- app/code/Magento/CatalogWidget/composer.json | 3 ++- app/code/Magento/SendFriend/composer.json | 3 ++- app/code/Magento/Translation/composer.json | 3 ++- app/code/Magento/Vault/composer.json | 3 ++- .../testsuite/Magento/Test/Integrity/DependencyTest.php | 2 +- 7 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index 5b5eeaf2b3dd7..58049f7bf0f93 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -22,11 +22,11 @@ "magento/module-sales": "*", "magento/module-ui": "*", "magento/module-vault": "*", - "magento/module-multishipping": "*" + "magento/module-multishipping": "*", + "magento/module-theme": "*" }, "suggest": { - "magento/module-checkout-agreements": "*", - "magento/module-theme": "*" + "magento/module-checkout-agreements": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index fa8daaabe5710..8023634fa074d 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -31,8 +31,7 @@ "magento/module-ui": "*", "magento/module-url-rewrite": "*", "magento/module-widget": "*", - "magento/module-wishlist": "*", - "magento/module-authorization": "*" + "magento/module-wishlist": "*" }, "suggest": { "magento/module-cookie": "*", diff --git a/app/code/Magento/CatalogWidget/composer.json b/app/code/Magento/CatalogWidget/composer.json index 6722d0df93752..8c1bd220a0f32 100644 --- a/app/code/Magento/CatalogWidget/composer.json +++ b/app/code/Magento/CatalogWidget/composer.json @@ -14,7 +14,8 @@ "magento/module-rule": "*", "magento/module-store": "*", "magento/module-widget": "*", - "magento/module-wishlist": "*" + "magento/module-wishlist": "*", + "magento/module-theme": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/SendFriend/composer.json b/app/code/Magento/SendFriend/composer.json index f06f1b4a9e3e3..064b45e97d6c5 100644 --- a/app/code/Magento/SendFriend/composer.json +++ b/app/code/Magento/SendFriend/composer.json @@ -11,7 +11,8 @@ "magento/module-customer": "*", "magento/module-store": "*", "magento/module-captcha": "*", - "magento/module-authorization": "*" + "magento/module-authorization": "*", + "magento/module-theme": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/Translation/composer.json b/app/code/Magento/Translation/composer.json index c01791c88f99f..511238aefe7f3 100644 --- a/app/code/Magento/Translation/composer.json +++ b/app/code/Magento/Translation/composer.json @@ -9,7 +9,8 @@ "magento/framework": "*", "magento/module-backend": "*", "magento/module-developer": "*", - "magento/module-store": "*" + "magento/module-store": "*", + "magento/module-theme" : "*" }, "suggest": { "magento/module-deploy": "*" diff --git a/app/code/Magento/Vault/composer.json b/app/code/Magento/Vault/composer.json index 7dc2e0be78640..c37bc51f9d432 100644 --- a/app/code/Magento/Vault/composer.json +++ b/app/code/Magento/Vault/composer.json @@ -12,7 +12,8 @@ "magento/module-payment": "*", "magento/module-quote": "*", "magento/module-sales": "*", - "magento/module-store": "*" + "magento/module-store": "*", + "magento/module-theme": "*" }, "type": "magento2-module", "license": [ diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index e52c31723725b..c890cee6f1dda 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -307,8 +307,8 @@ function ($matches) use ($contents, &$contentsWithoutHtml) { }, $contents ); + return $contentsWithoutHtml; } - return (string)file_get_contents($file); } From e99ddb823ac358249389693c7be0d66c295a507d Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Wed, 28 Aug 2019 21:46:54 +0000 Subject: [PATCH 510/841] Set magentoApiDataFixture --- .../Magento/GraphQl/Customer/CreateCustomerTest.php | 2 +- .../Magento/Customer/_files/customer_subscribe.php | 13 +++++++++++++ .../Customer/_files/customer_subscribe_rollback.php | 13 +++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index c5714012f38c9..5ebf76f3b6cc0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -276,7 +276,7 @@ public function testCreateCustomerIfNameEmpty() } /** - * @magentoConfigFixture default_store newsletter/general/active 0 + * @magentoApiDataFixture Magento/Customer/_files/customer_subscribe.php */ public function testCreateCustomerSubscribed() { diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php new file mode 100644 index 0000000000000..ab45a28a795bf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +$resourceConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Config\Model\ResourceModel\Config::class); +$resourceConfig->saveConfig( + 'newsletter/general/active', + false, + 'default', + 0 +); \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php new file mode 100644 index 0000000000000..35e10e52b6e67 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php @@ -0,0 +1,13 @@ +<?php +/** +* Copyright © Magento, Inc. All rights reserved. +* See COPYING.txt for license details. +*/ +// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 + +use Magento\Framework\App\Config\Storage\WriterInterface; +use Magento\TestFramework\Helper\Bootstrap; +$objectManager = Bootstrap::getObjectManager(); +/** @var Writer $configWriter */ +$configWriter = $objectManager->create(WriterInterface::class); +$configWriter->delete('newsletter/general/active'); \ No newline at end of file From 3bc203941125294fd3fabc9b2bacfd74da468a90 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 28 Aug 2019 16:52:19 -0500 Subject: [PATCH 511/841] MC-18512: Dynamically inject all searchable custom attributes for product filtering - Use FilterMatchTypeInput --- .../Model/Config/FilterAttributeReader.php | 19 ++++++++++++------- .../Model/Resolver/Products.php | 4 ++-- app/code/Magento/GraphQl/etc/schema.graphqls | 5 ++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php index b2cb4dca28bde..3238a1b6564ad 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php @@ -11,6 +11,7 @@ use Magento\Framework\GraphQl\Schema\Type\Entity\MapperInterface; use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; /** * Adds custom/eav attributes to product filter type in the GraphQL config. @@ -32,7 +33,7 @@ class FilterAttributeReader implements ReaderInterface */ private const FILTER_EQUAL_TYPE = 'FilterEqualTypeInput'; private const FILTER_RANGE_TYPE = 'FilterRangeTypeInput'; - private const FILTER_LIKE_TYPE = 'FilterLikeTypeInput'; + private const FILTER_MATCH_TYPE = 'FilterMatchTypeInput'; /** * @var MapperInterface @@ -74,7 +75,7 @@ public function read($scope = null) : array foreach ($typeNames as $typeName) { $config[$typeName]['fields'][$attributeCode] = [ 'name' => $attributeCode, - 'type' => $this->getFilterType($attribute->getFrontendInput()), + 'type' => $this->getFilterType($attribute), 'arguments' => [], 'required' => false, 'description' => sprintf('Attribute label: %s', $attribute->getDefaultFrontendLabel()) @@ -88,22 +89,26 @@ public function read($scope = null) : array /** * Map attribute type to filter type * - * @param string $attributeType + * @param Attribute $attribute * @return string */ - private function getFilterType($attributeType): string + private function getFilterType(Attribute $attribute): string { + if ($attribute->getAttributeCode() === 'sku') { + return self::FILTER_EQUAL_TYPE; + } + $filterTypeMap = [ 'price' => self::FILTER_RANGE_TYPE, 'date' => self::FILTER_RANGE_TYPE, 'select' => self::FILTER_EQUAL_TYPE, 'multiselect' => self::FILTER_EQUAL_TYPE, 'boolean' => self::FILTER_EQUAL_TYPE, - 'text' => self::FILTER_LIKE_TYPE, - 'textarea' => self::FILTER_LIKE_TYPE, + 'text' => self::FILTER_MATCH_TYPE, + 'textarea' => self::FILTER_MATCH_TYPE, ]; - return $filterTypeMap[$attributeType] ?? self::FILTER_LIKE_TYPE; + return $filterTypeMap[$attribute->getFrontendInput()] ?? self::FILTER_MATCH_TYPE; } /** diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index 3bc61a0fb3f2c..691f93e4148bc 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -97,8 +97,8 @@ public function resolve( //get product children fields queried $productFields = (array)$info->getFieldSelection(1); - - $searchCriteria = $this->searchApiCriteriaBuilder->build($args, isset($productFields['filters'])); + $includeAggregations = isset($productFields['filters']) || isset($productFields['aggregations']); + $searchCriteria = $this->searchApiCriteriaBuilder->build($args, $includeAggregations); $searchResult = $this->searchQuery->getResult($searchCriteria, $info, $args); if ($searchResult->getCurrentPage() > $searchResult->getTotalPages() && $searchResult->getTotalCount() > 0) { diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 997572471122f..69b822d4285b8 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -75,9 +75,8 @@ input FilterRangeTypeInput @doc(description: "Specifies which action will be per to: String } -input FilterLikeTypeInput @doc(description: "Specifies which action will be performed in a query ") { - like: String - eq: String +input FilterMatchTypeInput @doc(description: "Specifies which action will be performed in a query ") { + match: String } type SearchResultPageInfo @doc(description: "SearchResultPageInfo provides navigation for the query response") { From 50acd4f556ec313e4ad1ec6efc1739af4e8e9dbe Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Wed, 28 Aug 2019 20:04:20 -0500 Subject: [PATCH 512/841] MC-19712: Full page cache issue with multiple stores --- .../Store/App/Action/Plugin/Context.php | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Store/App/Action/Plugin/Context.php b/app/code/Magento/Store/App/Action/Plugin/Context.php index b5d2e3d913f4c..d661c9d92f3ee 100644 --- a/app/code/Magento/Store/App/Action/Plugin/Context.php +++ b/app/code/Magento/Store/App/Action/Plugin/Context.php @@ -13,6 +13,8 @@ use Magento\Framework\Exception\NotFoundException; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Api\StoreCookieManagerInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManager; use Magento\Store\Model\StoreManagerInterface; /** @@ -83,36 +85,37 @@ public function beforeDispatch( ); if (is_array($storeCode)) { if (!isset($storeCode['_data']['code'])) { - $this->processInvalidStoreRequested(); + $this->processInvalidStoreRequested($request); } $storeCode = $storeCode['_data']['code']; } if ($storeCode === '') { //Empty code - is an invalid code and it was given explicitly //(the value would be null if the code wasn't found). - $this->processInvalidStoreRequested(); + $this->processInvalidStoreRequested($request); } try { $currentStore = $this->storeManager->getStore($storeCode); + $this->updateContext($request, $currentStore); } catch (NoSuchEntityException $exception) { - $this->processInvalidStoreRequested($exception); + $this->processInvalidStoreRequested($request, $exception); } - - $this->updateContext($currentStore); } /** * Take action in case of invalid store requested. * + * @param RequestInterface $request * @param \Throwable|null $previousException * @return void * @throws NotFoundException */ private function processInvalidStoreRequested( + RequestInterface $request, \Throwable $previousException = null ) { $store = $this->storeManager->getStore(); - $this->updateContext($store); + $this->updateContext($request, $store); throw new NotFoundException( $previousException @@ -125,16 +128,28 @@ private function processInvalidStoreRequested( /** * Update context accordingly to the store found. * + * @param RequestInterface $request * @param StoreInterface $store * @return void * @throws \Magento\Framework\Exception\LocalizedException */ - private function updateContext(StoreInterface $store) + private function updateContext(RequestInterface $request, StoreInterface $store) { + switch (true) { + case $store->isUseStoreInUrl(): + $defaultStoreCode = $store->getCode(); + break; + case ScopeInterface::SCOPE_STORE == $request->getServerValue(StoreManager::PARAM_RUN_TYPE): + $defaultStoreCode = $request->getServerValue(StoreManager::PARAM_RUN_CODE); + break; + default: + $defaultStoreCode = $this->storeManager->getDefaultStoreView()->getCode(); + break; + } $this->httpContext->setValue( StoreManagerInterface::CONTEXT_STORE, $store->getCode(), - $store->isUseStoreInUrl() ? $store->getCode() : $this->storeManager->getDefaultStoreView()->getCode() + $defaultStoreCode ); /** @var StoreInterface $defaultStore */ From b4a7ca4c0bfc2d4e08074371675899ba3fc5edb3 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Thu, 29 Aug 2019 12:42:42 +0300 Subject: [PATCH 513/841] magento/magento2#22182: Static tests fix. --- app/code/Magento/Deploy/Package/Package.php | 27 +++++++++++++++++++ .../Email/Model/Template/FilterTest.php | 12 +++++---- .../PreProcessor/Adapter/Less/Processor.php | 9 ++++--- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Deploy/Package/Package.php b/app/code/Magento/Deploy/Package/Package.php index 68ce6407bda73..2e924d41a1b83 100644 --- a/app/code/Magento/Deploy/Package/Package.php +++ b/app/code/Magento/Deploy/Package/Package.php @@ -150,6 +150,8 @@ public function __construct( } /** + * Get area. + * * @return string */ public function getArea() @@ -158,6 +160,8 @@ public function getArea() } /** + * Get parent. + * * @return Package */ public function getParent() @@ -166,6 +170,8 @@ public function getParent() } /** + * Get theme. + * * @return string */ public function getTheme() @@ -174,6 +180,8 @@ public function getTheme() } /** + * Get locale. + * * @return string */ public function getLocale() @@ -204,6 +212,8 @@ public function isVirtual() } /** + * Get param. + * * @param string $name * @return mixed|null */ @@ -213,6 +223,8 @@ public function getParam($name) } /** + * Set param. + * * @param string $name * @param mixed $value * @return bool @@ -351,6 +363,8 @@ public function aggregate(Package $parentPackage = null) } /** + * Set parent. + * * @param Package $parent * @return bool */ @@ -371,6 +385,8 @@ public function getMap() } /** + * Get state. + * * @return int */ public function getState() @@ -379,6 +395,8 @@ public function getState() } /** + * Set state. + * * @param int $state * @return bool */ @@ -389,6 +407,8 @@ public function setState($state) } /** + * Get inheritance level. + * * @return int */ public function getInheritanceLevel() @@ -425,6 +445,7 @@ public function getParentMap() { $map = []; foreach ($this->getParentPackages() as $parentPackage) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge $map = array_merge($map, $parentPackage->getMap()); } return $map; @@ -441,8 +462,10 @@ public function getParentFiles($type = null) $files = []; foreach ($this->getParentPackages() as $parentPackage) { if ($type === null) { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge $files = array_merge($files, $parentPackage->getFiles()); } else { + // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge $files = array_merge($files, $parentPackage->getFilesByType($type)); } } @@ -480,6 +503,8 @@ public function getParentPackages() } /** + * Get pre processors. + * * @return Processor\ProcessorInterface[] */ public function getPreProcessors() @@ -488,6 +513,8 @@ public function getPreProcessors() } /** + * Get post processors. + * * @return Processor\ProcessorInterface[] */ public function getPostProcessors() diff --git a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php index cce497b296957..5b354dce5062f 100644 --- a/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php +++ b/dev/tests/integration/testsuite/Magento/Email/Model/Template/FilterTest.php @@ -408,10 +408,12 @@ public function inlinecssDirectiveThrowsExceptionWhenMissingParameterDataProvide protected function setUpDesignParams() { $themeCode = 'Vendor_EmailTest/custom_theme'; - $this->model->setDesignParams([ - 'area' => Area::AREA_FRONTEND, - 'theme' => $themeCode, - 'locale' => Locale::DEFAULT_SYSTEM_LOCALE, - ]); + $this->model->setDesignParams( + [ + 'area' => Area::AREA_FRONTEND, + 'theme' => $themeCode, + 'locale' => Locale::DEFAULT_SYSTEM_LOCALE, + ] + ); } } diff --git a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php index 99f3d47c863c7..8f40ab044c020 100644 --- a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php +++ b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php @@ -61,7 +61,6 @@ public function __construct( /** * @inheritdoc - * @throws ContentProcessorException */ public function processContent(File $asset) { @@ -77,7 +76,9 @@ public function processContent(File $asset) $content = $this->assetSource->getContent($asset); if (trim($content) === '') { - throw new ContentProcessorException(new Phrase(ContentProcessorInterface::ERROR_MESSAGE_PREFIX . 'LESS file is empty: ' . $path)); + throw new ContentProcessorException( + new Phrase('Compilation from source: LESS file is empty: ' . $path) + ); } $tmpFilePath = $this->temporaryFile->createFile($path, $content); @@ -88,7 +89,9 @@ public function processContent(File $asset) gc_enable(); if (trim($content) === '') { - throw new ContentProcessorException(new Phrase(ContentProcessorInterface::ERROR_MESSAGE_PREFIX . 'LESS file is empty: ' . $path)); + throw new ContentProcessorException( + new Phrase('Compilation from source: LESS file is empty: ' . $path) + ); } else { return $content; } From 21c576a8b2a756023d3a7977d39cc606a3e347d3 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Thu, 29 Aug 2019 12:58:43 +0300 Subject: [PATCH 514/841] magento/magento2#24053: Static tests fix. --- app/code/Magento/CatalogImportExport/Model/Import/Product.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 8ad8458c1be27..4ff995c2a872c 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1192,8 +1192,10 @@ protected function _initTypeModels() if ($model->isSuitable()) { $this->_productTypeModels[$productTypeName] = $model; } + // phpcs:disable Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge $this->_fieldsMap = array_merge($this->_fieldsMap, $model->getCustomFieldsMapping()); $this->_specialAttributes = array_merge($this->_specialAttributes, $model->getParticularAttributes()); + // phpcs:enable } $this->_initErrorTemplates(); // remove doubles From 49936baec526f950e3b6d3affd67e85299bc4fad Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Thu, 29 Aug 2019 13:03:35 +0300 Subject: [PATCH 515/841] magento/magento2#22478: Static test fix. --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index 82170929d8e73..5c9bffe85f3f0 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -261,6 +261,7 @@ define([ $(document).trigger('ajax:removeFromCart', { productIds: [productData['product_id']] }); + if (window.location.href.indexOf(this.shoppingCartUrl) === 0) { window.location.reload(); } From c1d221ee1ae0d1450a2361ff042abd6693e3b6cd Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Thu, 29 Aug 2019 13:31:18 +0300 Subject: [PATCH 516/841] MC-19438: Bundle Product Cart Pricing issue when adding tier price to bundle product and item is added twice to the cart --- .../BundleProductWithTierPriceInCartTest.xml | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml new file mode 100644 index 0000000000000..32a035e7eb1be --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="BundleProductWithTierPriceInCartTest"> + <annotations> + <features value="Bundle"/> + <stories value="Check that price of cart is correct when the bundle product added to the cart twice"/> + <title value="Customer should get the right subtotal in cart when the bundle product added to the cart twice"/> + <description value="Customer should be able to add one more bundle product to the cart and get the right price"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-19727"/> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + </before> + <after> + <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="ClearFiltersAfter"/> + <waitForPageLoad stepKey="waitForClearFilter"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + </after> + <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage"/> + <waitForPageLoad stepKey="waitForBundleProductCreatePageToLoad"/> + <actionGroup ref="fillMainBundleProductForm" stepKey="fillMainFieldsForBundle"/> + <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOption1"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="Option1"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOption2"> + <argument name="x" value="1"/> + <argument name="n" value="2"/> + <argument name="prodOneSku" value="$$simpleProduct2.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="Option2"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="addTierPriceProduct1"> + <argument name="group" value="General"/> + <argument name="quantity" value="1"/> + <argument name="price" value="Discount"/> + <argument name="amount" value="50"/> + </actionGroup> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="SignUpNewUser"> + <argument name="Customer" value="CustomerEntityOne"/> + </actionGroup> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> + <argument name="quantity" value="1"/> + </actionGroup> + <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCartAgain"> + <argument name="quantity" value="1"/> + </actionGroup> + <click stepKey="openMiniCart" selector="{{StorefrontMinicartSection.showCart}}"/> + <waitForPageLoad stepKey="waitForMiniCart"/> + <see stepKey="seeCartSubtotal" userInput="$246.00"/> + </test> +</tests> From 21b91d92bec2d2367c9c038b2ef676b04e1456b0 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Thu, 29 Aug 2019 18:36:38 +0700 Subject: [PATCH 517/841] Validate proxy port issue 24312 --- .../Magento/Paypal/etc/adminhtml/system/express_checkout.xml | 3 ++- .../Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml | 3 ++- app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml | 3 ++- .../Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml | 3 ++- app/code/Magento/Paypal/i18n/en_US.csv | 1 + 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 9301b5ffbde8f..04d5fae435816 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -117,8 +117,9 @@ <field id="use_proxy">1</field> </depends> </field> - <field id="proxy_port" translate="label" type="text" sortOrder="110" showInDefault="1" showInWebsite="1"> + <field id="proxy_port" translate="label comment" type="text" sortOrder="110" showInDefault="1" showInWebsite="1"> <label>Proxy Port</label> + <comment>Please enter at least 0 and at most 65535</comment> <config_path>paypal/wpp/proxy_port</config_path> <attribute type="shared">1</attribute> <depends> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml index 492d4b2ac9ce4..1ae0b52146c38 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml @@ -69,8 +69,9 @@ </depends> <attribute type="shared">1</attribute> </field> - <field id="proxy_port" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1"> + <field id="proxy_port" translate="label comment" type="text" sortOrder="90" showInDefault="1" showInWebsite="1"> <label>Proxy Port</label> + <comment>Please enter at least 0 and at most 65535</comment> <config_path>payment/payflow_advanced/proxy_port</config_path> <depends> <field id="use_proxy">1</field> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml index 2e25dd6a66124..ead70eca3fadd 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml @@ -70,8 +70,9 @@ </depends> <attribute type="shared">1</attribute> </field> - <field id="proxy_port" translate="label" type="text" sortOrder="80" showInDefault="1" showInWebsite="1"> + <field id="proxy_port" translate="label comment" type="text" sortOrder="80" showInDefault="1" showInWebsite="1"> <label>Proxy Port</label> + <comment>Please enter at least 0 and at most 65535</comment> <config_path>payment/payflow_link/proxy_port</config_path> <depends> <field id="use_proxy">1</field> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml index 91396d2a95ac2..c87a781f36c00 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro.xml @@ -65,8 +65,9 @@ </depends> <attribute type="shared">1</attribute> </field> - <field id="proxy_port" translate="label" type="text" sortOrder="90" showInDefault="1" showInWebsite="1"> + <field id="proxy_port" translate="label comment" type="text" sortOrder="90" showInDefault="1" showInWebsite="1"> <label>Proxy Port</label> + <comment>Please enter at least 0 and at most 65535</comment> <config_path>payment/payflowpro/proxy_port</config_path> <depends> <field id="use_proxy">1</field> diff --git a/app/code/Magento/Paypal/i18n/en_US.csv b/app/code/Magento/Paypal/i18n/en_US.csv index e7264a6de807f..4e47c4c1f9e9f 100644 --- a/app/code/Magento/Paypal/i18n/en_US.csv +++ b/app/code/Magento/Paypal/i18n/en_US.csv @@ -737,3 +737,4 @@ User,User "PayPal Credit","PayPal Credit" "PayPal Guest Checkout Credit Card Icons","PayPal Guest Checkout Credit Card Icons" "Elektronisches Lastschriftverfahren - German ELV","Elektronisches Lastschriftverfahren - German ELV" +"Please enter at least 0 and at most 65535","Please enter at least 0 and at most 65535" From c45c0e883137ae47ef2d8a54e151fb52785f1334 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 29 Aug 2019 15:08:55 +0300 Subject: [PATCH 518/841] MC-19260: Coupon code removed during tax/shipping calculation on checkout --- .../Rule/Action/Discount/CartFixedTest.php | 71 +++++++++++++---- .../coupon_cart_fixed_discount_rollback.php | 31 ++++++-- ..._fixed_discount_subtotal_with_discount.php | 58 -------------- ...upon_cart_fixed_subtotal_with_discount.php | 33 ++++++++ ..._fixed_subtotal_with_discount_rollback.php | 8 ++ .../SalesRule/_files/quote_with_coupon.php | 79 ------------------- 6 files changed, 123 insertions(+), 157 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php delete mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php 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 21f8d4a45432f..1f8d1781b3d2f 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 @@ -11,11 +11,14 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ProductRepository; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Api\Data\CartItemInterface; use Magento\Quote\Api\GuestCartItemRepositoryInterface; use Magento\Quote\Api\GuestCartManagementInterface; use Magento\Quote\Api\GuestCartTotalRepositoryInterface; use Magento\Quote\Api\GuestCouponManagementInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteIdMask; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\TestFramework\Helper\Bootstrap; @@ -47,15 +50,28 @@ class CartFixedTest extends \PHPUnit\Framework\TestCase */ private $objectManager; + /** + * @var SearchCriteriaBuilder + */ + private $criteriaBuilder; + + /** + * @var CartRepositoryInterface + */ + private $quoteRepository; + /** * @inheritdoc */ protected function setUp() { - $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->cartManagement = Bootstrap::getObjectManager()->create(GuestCartManagementInterface::class); - $this->couponManagement = Bootstrap::getObjectManager()->create(GuestCouponManagementInterface::class); - $this->cartItemRepository = Bootstrap::getObjectManager()->create(GuestCartItemRepositoryInterface::class); + $objectManager = Bootstrap::getObjectManager(); + $this->cartManagement = $objectManager->create(GuestCartManagementInterface::class); + $this->couponManagement = $objectManager->create(GuestCouponManagementInterface::class); + $this->cartItemRepository = $objectManager->create(GuestCartItemRepositoryInterface::class); + $this->criteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); + $this->quoteRepository = $objectManager->get(CartRepositoryInterface::class); + $this->objectManager = $objectManager; } /** @@ -64,6 +80,7 @@ protected function setUp() * @param array $productPrices * @return void * @magentoDbIsolation enabled + * @magentoAppIsolation enabled * @magentoDataFixture Magento/SalesRule/_files/coupon_cart_fixed_discount.php * @dataProvider applyFixedDiscountDataProvider */ @@ -96,25 +113,51 @@ public function testApplyFixedDiscount(array $productPrices): void /** * Applies fixed discount amount on whole cart and created order with it * - * @return void - * @magentoDataFixture Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php - * @magentoDataFixture Magento/SalesRule/_files/quote_with_coupon.php - * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoConfigFixture default_store carriers/freeshipping/active 1 + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php */ public function testOrderWithFixedDiscount(): void { - /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->objectManager->create(\Magento\Quote\Model\Quote::class); + $expectedGrandTotal = 5; + + $quote = $this->getQuote(); + $quote->getShippingAddress() + ->setShippingMethod('freeshipping_freeshipping') + ->setCollectShippingRates(true); + $quote->setCouponCode('CART_FIXED_DISCOUNT_15'); + $quote->collectTotals(); + $this->quoteRepository->save($quote); + + $this->assertEquals($expectedGrandTotal, $quote->getGrandTotal()); + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ - $quoteIdMask = $this->objectManager->create(\Magento\Quote\Model\QuoteIdMask::class); - $quote->load('test01', 'reserved_order_id'); + $quoteIdMask = $this->objectManager->create(QuoteIdMask::class); $quoteIdMask->load($quote->getId(), 'quote_id'); Bootstrap::getInstance()->reinitialize(); - $cartManagement = Bootstrap::getObjectManager()->create(GuestCartManagementInterface::class); $cartManagement->placeOrder($quoteIdMask->getMaskedId()); $order = $this->getOrder('test01'); - $this->assertEquals($quote->getGrandTotal(), $order->getGrandTotal()); + $this->assertEquals($expectedGrandTotal, $order->getGrandTotal()); + } + + /** + * Load cart from fixture. + * + * @return Quote + */ + private function getQuote(): Quote + { + $searchCriteria = $this->criteriaBuilder->addFilter('reserved_order_id', 'test01')->create(); + $carts = $this->quoteRepository->getList($searchCriteria) + ->getItems(); + if (!$carts) { + throw new \RuntimeException('Cart from fixture not found'); + } + + return array_shift($carts); } /** diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_rollback.php index 33a8b4285d8d7..f65cc3dc1cd57 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_rollback.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_rollback.php @@ -5,13 +5,32 @@ */ declare(strict_types=1); +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Registry; +use Magento\SalesRule\Api\RuleRepositoryInterface; +use Magento\SalesRule\Model\Rule; use Magento\TestFramework\Helper\Bootstrap; -/** @var Magento\Framework\Registry $registry */ -$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$objectManager = Bootstrap::getObjectManager(); -/** @var Magento\SalesRule\Model\Rule $rule */ -$rule = $registry->registry('cart_rule_fixed_discount_coupon'); -if ($rule) { - $rule->delete(); +/** @var Registry $registry */ +$registry = $objectManager->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('name', '15$ fixed discount on whole cart') + ->create(); +/** @var RuleRepositoryInterface $ruleRepository */ +$ruleRepository = $objectManager->get(RuleRepositoryInterface::class); +$items = $ruleRepository->getList($searchCriteria) + ->getItems(); +/** @var Rule $salesRule */ +$salesRule = array_pop($items); +if ($salesRule !== null) { + $ruleRepository->deleteById($salesRule->getRuleId()); } + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php deleted file mode 100644 index 04a91f2bdf55c..0000000000000 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_discount_subtotal_with_discount.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -use Magento\Customer\Model\GroupManagement; -use Magento\SalesRule\Api\CouponRepositoryInterface; -use Magento\SalesRule\Model\Coupon; -use Magento\SalesRule\Model\Rule; -use Magento\Store\Model\StoreManagerInterface; -use Magento\TestFramework\Helper\Bootstrap; - -$objectManager = Bootstrap::getObjectManager(); -$salesRule = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\SalesRule\Model\Rule::class); -$salesRule->setData( - [ - 'name' => '5$ fixed discount on whole cart', - 'is_active' => 1, - 'customer_group_ids' => [GroupManagement::NOT_LOGGED_IN_ID], - 'coupon_type' => Rule::COUPON_TYPE_SPECIFIC, - 'simple_action' => Rule::CART_FIXED_ACTION, - 'discount_amount' => 8, - 'discount_step' => 0, - 'stop_rules_processing' => 0, - 'website_ids' => [ - $objectManager->get(StoreManagerInterface::class)->getWebsite()->getId(), - ], - ] -); -$salesRule->getConditions()->loadArray([ - 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, - 'attribute' => null, - 'operator' => null, - 'value' => '1', - 'is_value_processed' => null, - 'aggregator' => 'any', - 'conditions' => - [ - [ - 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, - 'attribute' => 'base_subtotal_with_discount', - 'operator' => '>=', - 'value' => 9, - 'is_value_processed' => false - ], - ], -]); - -$salesRule->save(); - -// Create coupon and assign "5$ fixed discount" rule to this coupon. -$coupon = $objectManager->create(Coupon::class); -$coupon->setRuleId($salesRule->getId()) - ->setCode('CART_FIXED_DISCOUNT_5') - ->setType(0); -$objectManager->get(CouponRepositoryInterface::class)->save($coupon); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php new file mode 100644 index 0000000000000..195aab840c338 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +include __DIR__ . '/coupon_cart_fixed_discount.php'; + +use Magento\SalesRule\Model\ResourceModel\Rule as ResourceModel; +use Magento\SalesRule\Model\Rule\Condition\Address; +use Magento\SalesRule\Model\Rule\Condition\Combine; + +$salesRule->getConditions()->loadArray([ + 'type' => Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'any', + 'conditions' => + [ + [ + 'type' => Address::class, + 'attribute' => 'base_subtotal_with_discount', + 'operator' => '>=', + 'value' => 9, + 'is_value_processed' => false + ], + ], +]); +$salesRule->setDiscountAmount(5); +$objectManager->get(ResourceModel::class)->save($salesRule); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php new file mode 100644 index 0000000000000..3e34f9c34c275 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +include __DIR__ . '/coupon_cart_fixed_discount_rollback.php'; \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php deleted file mode 100644 index e6148c3e2a4e2..0000000000000 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/quote_with_coupon.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -use Magento\Quote\Api\GuestCouponManagementInterface; -use Magento\TestFramework\Helper\Bootstrap; - -\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea('frontend'); -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); -$product->setTypeId('simple') - ->setId(1) - ->setAttributeSetId(4) - ->setName('Simple Product') - ->setSku('simple') - ->setPrice(10) - ->setTaxClassId(0) - ->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' => 100, - 'is_in_stock' => 1, - 'manage_stock' => 1, - ] - )->save(); - -$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); -$product = $productRepository->get('simple'); - -$addressData = include __DIR__ . '/../../../Magento/Sales/_files/address_data.php'; -$billingAddress = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Quote\Model\Quote\Address::class, - ['data' => $addressData] -); -$billingAddress->setAddressType('billing'); - -$shippingAddress = clone $billingAddress; -$shippingAddress->setId(null)->setAddressType('shipping'); - -$store = Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->get(\Magento\Store\Model\StoreManagerInterface::class) - ->getStore(); - -/** @var \Magento\Quote\Model\Quote $quote */ -$quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Quote\Model\Quote::class); -$quote->setCustomerIsGuest(true) - ->setStoreId($store->getId()) - ->setReservedOrderId('test01') - ->setBillingAddress($billingAddress) - ->setShippingAddress($shippingAddress) - ->addProduct($product); -$quote->getPayment()->setMethod('checkmo'); -$quote->getShippingAddress()->setShippingMethod('flatrate_flatrate')->setCollectShippingRates(true); -$quote->setIsMultiShipping('1'); -$quote->collectTotals(); - -$quoteRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->get(\Magento\Quote\Api\CartRepositoryInterface::class); -$quoteRepository->save($quote); - -/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ -$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) - ->create(); -$quoteIdMask->setQuoteId($quote->getId()); -$quoteIdMask->setDataChanges(true); -$quoteIdMask->save(); - -$couponCode = 'CART_FIXED_DISCOUNT_5'; -$couponManagement = Bootstrap::getObjectManager()->create(GuestCouponManagementInterface::class); -$couponManagement->set($quoteIdMask->getMaskedId(), $couponCode); - - From 2fbe4f094882c6787d6c5a5bd96b978774f4c2fd Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Thu, 29 Aug 2019 17:39:33 +0530 Subject: [PATCH 519/841] Correct spelling --- app/code/Magento/GiftMessage/Helper/Message.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/GiftMessage/Helper/Message.php b/app/code/Magento/GiftMessage/Helper/Message.php index 55cebe26c1fc2..ee0a29ef8f065 100644 --- a/app/code/Magento/GiftMessage/Helper/Message.php +++ b/app/code/Magento/GiftMessage/Helper/Message.php @@ -191,7 +191,7 @@ public function isMessagesAllowed($type, \Magento\Framework\DataObject $entity, } /** - * Check availablity of gift messages from store config if flag eq 2. + * Check availability of gift messages from store config if flag eq 2. * * @param bool $productConfig * @param \Magento\Store\Model\Store|int|null $store From 49efcb1b68c0bf0714a1d4bcfeebfb49f83e35cb Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 29 Aug 2019 16:26:00 +0300 Subject: [PATCH 520/841] MC-19260: Coupon code removed during tax/shipping calculation on checkout --- .../Rule/Action/Discount/CartFixedTest.php | 1 + ...upon_cart_fixed_subtotal_with_discount.php | 34 ++++++++++--------- ..._fixed_subtotal_with_discount_rollback.php | 2 +- 3 files changed, 20 insertions(+), 17 deletions(-) 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 1f8d1781b3d2f..df26c1cc48f7b 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 @@ -27,6 +27,7 @@ * Tests for Magento\SalesRule\Model\Rule\Action\Discount\CartFixed. * * @magentoAppArea frontend + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CartFixedTest extends \PHPUnit\Framework\TestCase { diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php index 195aab840c338..b0f21a47a079e 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount.php @@ -11,23 +11,25 @@ use Magento\SalesRule\Model\Rule\Condition\Address; use Magento\SalesRule\Model\Rule\Condition\Combine; -$salesRule->getConditions()->loadArray([ - 'type' => Combine::class, - 'attribute' => null, - 'operator' => null, - 'value' => '1', - 'is_value_processed' => null, - 'aggregator' => 'any', - 'conditions' => - [ +$salesRule->getConditions()->loadArray( + [ + 'type' => Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'any', + 'conditions' => [ - 'type' => Address::class, - 'attribute' => 'base_subtotal_with_discount', - 'operator' => '>=', - 'value' => 9, - 'is_value_processed' => false + [ + 'type' => Address::class, + 'attribute' => 'base_subtotal_with_discount', + 'operator' => '>=', + 'value' => 9, + 'is_value_processed' => false + ], ], - ], -]); + ] +); $salesRule->setDiscountAmount(5); $objectManager->get(ResourceModel::class)->save($salesRule); diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php index 3e34f9c34c275..dc88b210cb706 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/coupon_cart_fixed_subtotal_with_discount_rollback.php @@ -5,4 +5,4 @@ */ declare(strict_types=1); -include __DIR__ . '/coupon_cart_fixed_discount_rollback.php'; \ No newline at end of file +include __DIR__ . '/coupon_cart_fixed_discount_rollback.php'; From 26653c9bf1b8eede1f58fbe471fdaf34e20a5cca Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Wed, 28 Aug 2019 15:52:11 +0300 Subject: [PATCH 521/841] MAGETWO-44170: Not pass function test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Fix modularity issue --- ...AdminProductTypeSwitchingOnEditingTest.xml | 52 +----------- ...AdminProductTypeSwitchingOnEditingTest.xml | 76 ++---------------- ...AdminProductTypeSwitchingOnEditingTest.xml | 79 +++++++++++++++++++ 3 files changed, 88 insertions(+), 119 deletions(-) create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 45f404478809a..42623f43d4e82 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -11,6 +11,7 @@ <test name="AdminVirtualProductTypeSwitchingToDownloadableProductTest"> <annotations> <features value="Catalog"/> + <stories value="Switch product type"/> <title value="Virtual product type switching on editing to Downloadable product"/> <description value="Virtual product type switching on editing to Downloadable product"/> <testCaseId value="MC-17954"/> @@ -61,6 +62,7 @@ <test name="AdminDownloadableProductTypeSwitchingToSimpleProductTest" extends="AdminVirtualProductTypeSwitchingToDownloadableProductTest"> <annotations> <features value="Catalog"/> + <stories value="Switch product type"/> <title value="Downloadable product type switching on editing to Simple product"/> <description value="Downloadable product type switching on editing to Simple product"/> <testCaseId value="MC-17955"/> @@ -91,54 +93,4 @@ <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertSimpleProductInStock"/> <dontSeeElement selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}" stepKey="dontSeeDownloadableLink" /> </test> - <test name="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> - <annotations> - <features value="Catalog"/> - <title value="Simple product type switching on editing to downloadable product"/> - <description value="Simple product type switching on editing to downloadable product"/> - <testCaseId value="MC-17956"/> - <useCaseId value="MAGETWO-44170"/> - <severity value="MAJOR"/> - <group value="catalog"/> - </annotations> - <before> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Create product--> - <comment userInput="Create product" stepKey="commentCreateProduct"/> - <createData entity="SimpleProduct2" stepKey="createProduct"/> - </before> - <after> - <!--Delete product--> - <comment userInput="Delete product" stepKey="commentDeleteProduct"/> - <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> - <!--Change product type to Downloadable--> - <comment userInput="Change product type to Downloadable" stepKey="commentCreateDownloadable"/> - <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToDownloadableProductPage"/> - <waitForPageLoad stepKey="waitForDownloadableProductPageLoad"/> - <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has no weight" stepKey="selectNoWeightForProduct"/> - <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> - <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> - <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLink"> - <argument name="link" value="downloadableLinkWithMaxDownloads"/> - </actionGroup> - <actionGroup ref="saveProductForm" stepKey="saveDownloadableProductForm"/> - <!--Assert downloadable product on Admin product page grid--> - <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertDownloadableProductOnAdmin"/> - <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> - <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> - <argument name="sku" value="$$createProduct.sku$$"/> - </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Downloadable Product" stepKey="seeDownloadableProductTypeInGrid"/> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearDownloadableProductFilters"/> - <!--Assert downloadable product on storefront--> - <comment userInput="Assert downloadable product on storefront" stepKey="commentAssertDownloadableProductOnStorefront"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openDownloadableProductPage"/> - <waitForPageLoad stepKey="waitForStorefrontDownloadableProductPageLoad"/> - <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertDownloadableProductInStock"/> - <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> - <seeElement selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}" stepKey="seeDownloadableLink" /> - </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 071ee8d18920f..8253dcf58f9fd 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -11,6 +11,7 @@ <test name="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> <annotations> <features value="Catalog"/> + <stories value="Switch product type"/> <title value="Simple product type switching on editing to configurable product"/> <description value="Simple product type switching on editing to configurable product"/> <testCaseId value="MAGETWO-29633"/> @@ -60,8 +61,8 @@ </actionGroup> <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> - <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductNameInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductNameInGrid2"/> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> <!--Assert configurable product on storefront--> <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> @@ -75,6 +76,7 @@ <test name="AdminConfigurableProductTypeSwitchingToVirtualProductTest" extends="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> <annotations> <features value="Catalog"/> + <stories value="Switch product type"/> <title value="Configurable product type switching on editing to virtual product"/> <description value="Configurable product type switching on editing to virtual product"/> <testCaseId value="MC-17952"/> @@ -113,6 +115,7 @@ <test name="AdminVirtualProductTypeSwitchingToConfigurableProductTest"> <annotations> <features value="Catalog"/> + <stories value="Switch product type"/> <title value="Virtual product type switching on editing to configurable product"/> <description value="Virtual product type switching on editing to configurable product"/> <testCaseId value="MC-17953"/> @@ -163,8 +166,8 @@ </actionGroup> <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeConfigurableProductNameInGrid"/> <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeConfigurableProductTypeInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfigurableProductSkuInGrid1"/> - <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfigurableProductSkuInGrid2"/> + <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeConfigurableProductNameInGrid1"/> + <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeConfigurableProductNameInGrid2"/> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearConfigurableProductFilters"/> <!--Assert configurable product on storefront--> <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigurableProductOnStorefront"/> @@ -175,69 +178,4 @@ <see userInput="option1" stepKey="verifyConfigurableProductOption1Exists"/> <see userInput="option2" stepKey="verifyConfigurableProductOption2Exists"/> </test> - <test name="AdminDownloadableProductTypeSwitchingToConfigurableProductTest" extends="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> - <annotations> - <features value="Catalog"/> - <title value="Downloadable product type switching on editing to configurable product"/> - <description value="Downloadable product type switching on editing to configurable product"/> - <testCaseId value="MC-17957"/> - <useCaseId value="MAGETWO-44170"/> - <severity value="MAJOR"/> - <group value="catalog"/> - </annotations> - <before> - <!--Create attribute with options--> - <comment userInput="Create attribute with options" stepKey="commentCreateAttributeWithOptions"/> - <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttribute"/> - <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOptionOne"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOptionTwo"> - <requiredEntity createDataKey="createConfigProductAttribute"/> - </createData> - </before> - <after> - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Delete product--> - <comment userInput="Delete product" stepKey="commentDeleteProduct"/> - <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteAttribute"/> - <actionGroup ref="deleteAllDuplicateProductUsingProductGrid" stepKey="deleteAllDuplicateProducts"> - <argument name="product" value="$$createProduct$$"/> - </actionGroup> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> - <!--Add configurations to product--> - <comment userInput="Add configurations to product" stepKey="commentAddConfigs"/> - <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToSimpleProductPage"/> - <waitForPageLoad stepKey="waitForSimpleProductPageLoad"/> - <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection"/> - <uncheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> - <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForProduct"/> - <actionGroup ref="saveProductForm" stepKey="saveDownloadssdableProductForm"/> - <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="setupConfigurations"> - <argument name="attributeCode" value="$$createConfigProductAttribute.attribute_code$$"/> - </actionGroup> - <actionGroup ref="saveConfiguredProduct" stepKey="saveConfigProductForm"/> - <!--Assert configurable product on Admin product page grid--> - <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertConfigProductOnAdmin"/> - <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPageForConfig"/> - <click selector="{{AdminProductGridSection.columnHeader('ID')}}" stepKey="clickIdHeaderToSortAsc"/> - <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySkuFroConfig"> - <argument name="sku" value="$$createProduct.sku$$"/> - </actionGroup> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeProductNameInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Configurable Product" stepKey="seeProductTypeInGrid"/> - <see selector="{{AdminProductGridSection.productGridCell('2', 'Name')}}" userInput="$$createProduct.name$$-option1" stepKey="seeProductSkuInGrid1"/> - <see selector="{{AdminProductGridSection.productGridCell('3', 'Name')}}" userInput="$$createProduct.name$$-option2" stepKey="seeProductSkuInGrid2"/> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> - <!--Assert configurable product on storefront--> - <comment userInput="Assert configurable product on storefront" stepKey="commentAssertConfigProductOnStorefront"/> - <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openProductPage"/> - <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> - <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertInStock"/> - <click selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="clickAttributeDropDown"/> - <see userInput="option1" stepKey="verifyOption1Exists"/> - <see userInput="option2" stepKey="verifyOption2Exists"/> - </test> </tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml new file mode 100644 index 0000000000000..80e86ab4d747c --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDownloadableProductTypeSwitchingToConfigurableProductTest" extends="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Switch product type"/> + <title value="Downloadable product type switching on editing to configurable product"/> + <description value="Downloadable product type switching on editing to configurable product"/> + <testCaseId value="MC-17957"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <!-- Open Dropdown and select downloadable product option --> + <click selector="{{AdminProductDownloadableSection.sectionHeader}}" stepKey="openDownloadableSection" after="waitForSimpleProductPageLoad"/> + <uncheckOption selector="{{AdminProductDownloadableSection.isDownloadableProduct}}" stepKey="checkOptionIsDownloadable" after="openDownloadableSection"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has weight" stepKey="selectWeightForProduct" after="checkOptionIsDownloadable"/> + <actionGroup ref="saveProductForm" stepKey="saveDownloadableProductForm" after="selectWeightForProduct"/> + </test> + <test name="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Switch product type"/> + <title value="Simple product type switching on editing to downloadable product"/> + <description value="Simple product type switching on editing to downloadable product"/> + <testCaseId value="MC-17956"/> + <useCaseId value="MAGETWO-44170"/> + <severity value="MAJOR"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create product--> + <comment userInput="Create product" stepKey="commentCreateProduct"/> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + <after> + <!--Delete product--> + <comment userInput="Delete product" stepKey="commentDeleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Change product type to Downloadable--> + <comment userInput="Change product type to Downloadable" stepKey="commentCreateDownloadable"/> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="gotToDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForDownloadableProductPageLoad"/> + <selectOption selector="{{AdminProductFormSection.productWeightSelect}}" userInput="This item has no weight" stepKey="selectNoWeightForProduct"/> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" stepKey="checkOptionPurchaseSeparately"/> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveDownloadableProductForm"/> + <!--Assert downloadable product on Admin product page grid--> + <comment userInput="Assert configurable product in Admin product page grid" stepKey="commentAssertDownloadableProductOnAdmin"/> + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="goToCatalogProductPage"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> + <argument name="sku" value="$$createProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="$$createProduct.name$$" stepKey="seeDownloadableProductNameInGrid"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Type')}}" userInput="Downloadable Product" stepKey="seeDownloadableProductTypeInGrid"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearDownloadableProductFilters"/> + <!--Assert downloadable product on storefront--> + <comment userInput="Assert downloadable product on storefront" stepKey="commentAssertDownloadableProductOnStorefront"/> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="openDownloadableProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontDownloadableProductPageLoad"/> + <see userInput="IN STOCK" selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="assertDownloadableProductInStock"/> + <scrollTo selector="{{StorefrontDownloadableProductSection.downloadableLinkBlock}}" stepKey="scrollToLinksInStorefront"/> + <seeElement selector="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLinkWithMaxDownloads.title)}}" stepKey="seeDownloadableLink" /> + </test> +</tests> From dfdb152c5051f45e1a91c438c73025eeba65484b Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Thu, 29 Aug 2019 19:30:38 +0530 Subject: [PATCH 522/841] add number validation for sitemap file limits --- app/code/Magento/Sitemap/etc/adminhtml/system.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Sitemap/etc/adminhtml/system.xml b/app/code/Magento/Sitemap/etc/adminhtml/system.xml index c65311fc5e0d0..06dbaa79695be 100644 --- a/app/code/Magento/Sitemap/etc/adminhtml/system.xml +++ b/app/code/Magento/Sitemap/etc/adminhtml/system.xml @@ -83,10 +83,12 @@ <label>Sitemap File Limits</label> <field id="max_lines" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum No of URLs Per File</label> + <validate>validate-number</validate> </field> <field id="max_file_size" translate="label comment" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum File Size</label> <comment>File size in bytes.</comment> + <validate>validate-number</validate> </field> </group> <group id="search_engines" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1"> From c4ea11883671af962804a233e8dc1c08360034a9 Mon Sep 17 00:00:00 2001 From: Krissy Hiserote <khiserote@magento.com> Date: Thu, 29 Aug 2019 09:26:13 -0500 Subject: [PATCH 523/841] MC-16650: Product Attribute Type Price Not Displaying - remove decimal filter integration test --- .../attribute_special_price_filterable.php | 12 --- .../_files/multiple_visible_products.php | 100 ------------------ .../multiple_visible_products_rollback.php | 35 ------ .../Model/Layer/Filter/DecimalTest.php | 36 +------ 4 files changed, 1 insertion(+), 182 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php delete mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php delete mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php deleted file mode 100644 index eedff7aaefb1d..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -/** @var $installer \Magento\Catalog\Setup\CategorySetup */ -$installer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Catalog\Setup\CategorySetup::class -); - -$installer->updateAttribute('catalog_product', 'special_price', 'is_filterable', 1); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php deleted file mode 100644 index f5c22c570911f..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products.php +++ /dev/null @@ -1,100 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); -$category->isObjectNew(true); -$category->setId( - 100 -)->setCreatedAt( - '2014-06-23 09:50:07' -)->setName( - 'Category 100' -)->setParentId( - 2 -)->setPath( - '1/2/100' -)->setLevel( - 2 -)->setAvailableSortBy( - ['position', 'name'] -)->setDefaultSortBy( - 'name' -)->setIsActive( - true -)->setPosition( - 1 -)->save(); - -/** @var $product \Magento\Catalog\Model\Product */ -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); -$product->isObjectNew(true); -$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId(10) - ->setAttributeSetId(4) - ->setName('Simple Product1') - ->setSku('simple1') - ->setTaxClassId('none') - ->setDescription('description') - ->setShortDescription('short description') - ->setOptionsContainer('container1') - ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_IN_CART) - ->setPrice(15) - ->setWeight(10) - ->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) - ->setWebsiteIds([1]) - ->setCategoryIds([100]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) - ->setSpecialPrice('10') - ->save(); - -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); -$product->isObjectNew(true); -$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId(11) - ->setAttributeSetId(4) - ->setName('Simple Product2') - ->setSku('simple2') - ->setTaxClassId('none') - ->setDescription('description') - ->setShortDescription('short description') - ->setOptionsContainer('container1') - ->setMsrpDisplayActualPriceType(\Magento\Msrp\Model\Product\Attribute\Source\Type::TYPE_ON_GESTURE) - ->setPrice(25) - ->setWeight(20) - ->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) - ->setWebsiteIds([1]) - ->setCategoryIds([100]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 50, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) - ->setSpecialPrice('20') - ->save(); - -$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); -$product->isObjectNew(true); -$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) - ->setId(12) - ->setAttributeSetId(4) - ->setName('Simple Product3') - ->setSku('simple3') - ->setTaxClassId('none') - ->setDescription('description') - ->setShortDescription('short description') - ->setPrice(35) - ->setWeight(30) - ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) - ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) - ->setWebsiteIds([1]) - ->setCategoryIds([100]) - ->setStockData(['use_config_manage_stock' => 1, 'qty' => 140, 'is_qty_decimal' => 0, 'is_in_stock' => 1]) - ->setSpecialPrice('30') - ->save(); \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php deleted file mode 100644 index 76876c39b7d72..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiple_visible_products_rollback.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - -/** @var \Magento\Framework\Registry $registry */ -$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); - -$registry->unregister('isSecureArea'); -$registry->register('isSecureArea', true); - -/** @var $category \Magento\Catalog\Model\Category */ -$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); -$category->load(100); -if ($category->getId()) { - $category->delete(); -} - -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ -$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); - -foreach (['simple1', 'simple2', 'simple3'] as $sku) { - try { - $product = $productRepository->get($sku, false, null, true); - $productRepository->delete($product); - } catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { - //Product already removed - } -} - -$registry->unregister('isSecureArea'); -$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php index 63973ac0fbe20..b75a984178f24 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Layer/Filter/DecimalTest.php @@ -48,41 +48,7 @@ protected function setUp() ->create(\Magento\CatalogSearch\Model\Layer\Filter\Decimal::class, ['layer' => $layer]); $this->_model->setAttributeModel($attribute); } - - /** - * Test the product collection returns the correct number of items after the filter is applied. - * - * @magentoDataFixture Magento/Catalog/Model/Layer/Filter/_files/attribute_special_price_filterable.php - * @magentoDataFixture Magento/Catalog/_files/multiple_visible_products.php - * @magentoDbIsolation disabled - */ - public function testApplyProductCollection() - { - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $category = $objectManager->create(\Magento\Catalog\Model\Category::class); - $category->load(100); - $this->_model->getLayer()->setCurrentCategory($category); - - /** @var $attribute \Magento\Catalog\Model\Entity\Attribute */ - $attribute = $objectManager->create(\Magento\Catalog\Model\Entity\Attribute::class); - $attribute->loadByCode('catalog_product', 'special_price'); - $this->_model->setAttributeModel($attribute); - - /** @var $objectManager \Magento\TestFramework\ObjectManager */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - /** @var $request \Magento\TestFramework\Request */ - $request = $objectManager->get(\Magento\TestFramework\Request::class); - $request->setParam('special_price', '10-20'); - $result = $this->_model->apply($request); - $collection = $this->_model->getLayer()->getProductCollection(); - $size = $collection->getSize(); - $this->assertEquals( - 1, - $size - ); - } - + /** * Test the filter label is correct */ From 785cdcdfaa618ed77d896dcda5658b17514c61c8 Mon Sep 17 00:00:00 2001 From: Nagamaiah333 <54108580+Nagamaiah333@users.noreply.github.com> Date: Thu, 29 Aug 2019 20:03:26 +0530 Subject: [PATCH 524/841] Fixed the Spacing issue in grid Fixed the Spacing issue in grid --- .../Magento/backend/web/css/source/forms/_controls.less | 1 + 1 file changed, 1 insertion(+) diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less index 4c32405f2c995..c6f39e8e8840d 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less @@ -175,6 +175,7 @@ option:empty { line-height: @field-control__line-height; padding-bottom: @field-control__padding-bottom; padding-top: @field-control__padding-top; + margin-left: @action__outer-indent; + [class*='admin__control-'] { margin-left: @action__outer-indent; From bfb57ec3bc426a9605682eab61dd12a8ae21da23 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Thu, 29 Aug 2019 14:34:41 +0000 Subject: [PATCH 525/841] Corrected newsLetterConfig --- .../CustomerGraphQl/Model/Resolver/CreateCustomer.php | 6 +++--- .../Magento/Customer/_files/customer_subscribe.php | 2 +- .../Magento/Customer/_files/customer_subscribe_rollback.php | 4 +++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index b7ac4d5ea5fdd..18e417bb5edfe 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -33,19 +33,19 @@ class CreateCustomer implements ResolverInterface /** * @var Config */ - private $newsletterConfig; + private $newsLetterConfig; /** * CreateCustomer constructor. * * @param ExtractCustomerData $extractCustomerData * @param CreateCustomerAccount $createCustomerAccount - * @param Config $config + * @param Config $newsLetterConfig */ public function __construct( ExtractCustomerData $extractCustomerData, CreateCustomerAccount $createCustomerAccount, - Config $newsletterConfig + Config $newsLetterConfig ) { $this->newsLetterConfig = $newsLetterConfig; $this->extractCustomerData = $extractCustomerData; diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php index ab45a28a795bf..58eb11fb00e8d 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php @@ -10,4 +10,4 @@ false, 'default', 0 -); \ No newline at end of file +); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php index 35e10e52b6e67..2a84fd5556eb7 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php @@ -5,9 +5,11 @@ */ // TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 +declare(strict_types=1); + use Magento\Framework\App\Config\Storage\WriterInterface; use Magento\TestFramework\Helper\Bootstrap; $objectManager = Bootstrap::getObjectManager(); /** @var Writer $configWriter */ $configWriter = $objectManager->create(WriterInterface::class); -$configWriter->delete('newsletter/general/active'); \ No newline at end of file +$configWriter->delete('newsletter/general/active'); From 8b7aa7e7e62e2c7b266e4730737536f395ab2f2e Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Thu, 29 Aug 2019 09:40:21 -0500 Subject: [PATCH 526/841] magento-engcom/magento2ce#3173: Static call eliminated --- app/code/Magento/Widget/Model/DeleteWidgetById.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Widget/Model/DeleteWidgetById.php b/app/code/Magento/Widget/Model/DeleteWidgetById.php index dc48f6896293a..4c5d23d5a056b 100644 --- a/app/code/Magento/Widget/Model/DeleteWidgetById.php +++ b/app/code/Magento/Widget/Model/DeleteWidgetById.php @@ -68,7 +68,12 @@ private function getWidgetById(int $instanceId): WidgetInstance $this->resourceModel->load($widgetInstance, $instanceId); if (!$widgetInstance->getId()) { - throw NoSuchEntityException::singleField('instance_id', $instanceId); + throw new NoSuchEntityException( + __( + 'No such entity with instance_id = %instance_id', + ['instance_id' => $instanceId] + ) + ); } return $widgetInstance; From 64d4b3781ae5d77bb237148378b00d261da4a647 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Thu, 29 Aug 2019 09:47:54 -0500 Subject: [PATCH 527/841] Make constants private --- app/code/Magento/Cron/Console/Command/CronInstallCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php index 131d0a7ca7e3e..5e30076c21e76 100644 --- a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php +++ b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php @@ -21,8 +21,8 @@ */ class CronInstallCommand extends Command { - const COMMAND_OPTION_FORCE = 'force'; - const COMMAND_OPTION_NON_OPTIONAL = 'non-optional'; + private const COMMAND_OPTION_FORCE = 'force'; + private const COMMAND_OPTION_NON_OPTIONAL = 'non-optional'; /** * @var CrontabManagerInterface From e20be334c7aec9359a91bda77df632e5c946cdab Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Thu, 29 Aug 2019 12:44:05 -0500 Subject: [PATCH 528/841] Added UpdateDownloadableCartItemsTest with testUpdateDownloadableCartItemQuantity and testRemoveCartItemIfQuantityIsZero validations --- .../UpdateDownloadableCartItemsTest.php | 202 ++++++++++++++++++ ...active_quote_with_downloadable_product.php | 40 ++++ ...ote_with_downloadable_product_rollback.php | 8 + 3 files changed, 250 insertions(+) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/UpdateDownloadableCartItemsTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product.php create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/UpdateDownloadableCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/UpdateDownloadableCartItemsTest.php new file mode 100644 index 0000000000000..ae533252f14c0 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/UpdateDownloadableCartItemsTest.php @@ -0,0 +1,202 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\DownloadableProduct; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Framework\ObjectManager\ObjectManager; +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\GraphQl\Quote\GetQuoteItemIdByReservedQuoteIdAndSku; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Item; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test cases for adding downloadable product to cart. + */ +class UpdateDownloadableCartItemsTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var GetQuoteItemIdByReservedQuoteIdAndSku + */ + private $getQuoteItemIdByReservedQuoteIdAndSku; + + /** + * @var QuoteFactory + */ + private $quoteFactory; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $this->objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->quoteFactory = $this->objectManager->get(QuoteFactory::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->quoteResource = $this->objectManager->get(QuoteResource::class); + $this->getQuoteItemIdByReservedQuoteIdAndSku = $this->objectManager->get( + GetQuoteItemIdByReservedQuoteIdAndSku::class + ); + } + + /** + * Update a downloadable product into shopping cart when "Links can be purchased separately" is enabled + * + * @magentoApiDataFixture Magento/Checkout/_files/active_quote_with_downloadable_product.php + */ + public function testUpdateDownloadableCartItemQuantity() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1'); + $sku = 'downloadable-product'; + $qty = 1; + $finalQty = $qty + 1; + $links = $this->getProductsLinks($sku); + $linkId = key($links); + + $query = <<<MUTATION +mutation { + addDownloadableProductsToCart( + input: { + cart_id: "{$maskedQuoteId}", + cart_items: [ + { + data: { + quantity: {$qty}, + sku: "{$sku}" + }, + downloadable_product_links: [ + { + link_id: {$linkId} + } + ] + } + ] + } + ) { + cart { + items { + product { + sku + } + quantity + } + } + } +} +MUTATION; + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('items', $response['addDownloadableProductsToCart']['cart']); + self::assertCount(1, $response['addDownloadableProductsToCart']['cart']['items']); + self::assertEquals($finalQty, $response['addDownloadableProductsToCart']['cart']['items'][0]['quantity']); + self::assertEquals($sku, $response['addDownloadableProductsToCart']['cart']['items'][0]['product']['sku']); + } + + /** + * Update a downloadable product into shopping cart when "Links can be purchased separately" is enabled + * + * @magentoApiDataFixture Magento/Checkout/_files/active_quote_with_downloadable_product.php + */ + public function testRemoveCartItemIfQuantityIsZero() + { + $reservedOrderId = "test_order_1"; + $sku = "downloadable-product"; + + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + /** @var Quote $quote */ + $quote = $this->quoteFactory->create(); + $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id'); + $qty = 0; + + $itemId = 0; + /** @var Item $item */ + foreach ($quote->getAllItems() as $item) { + if ($item->getSku() == $sku) { + $itemId = $item->getId(); + } + } + + $query = <<<MUTATION +mutation { + updateCartItems(input: { + cart_id: "{$maskedQuoteId}" + cart_items:[ + { + cart_item_id: {$itemId} + quantity: {$qty} + } + ] + }) { + cart { + items { + id + quantity + } + } + } +} +MUTATION; + $response = $this->graphQlMutation($query); + + self::assertArrayHasKey('updateCartItems', $response); + self::assertArrayHasKey('cart', $response['updateCartItems']); + + $responseCart = $response['updateCartItems']['cart']; + self::assertCount(0, $responseCart['items']); + } + + /** + * Function returns array of all product's links + * + * @param string $sku + * @return array + */ + private function getProductsLinks(string $sku) : array + { + $result = []; + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + + $product = $productRepository->get($sku, false, null, true); + + foreach ($product->getDownloadableLinks() as $linkObject) { + $result[$linkObject->getLinkId()] = [ + 'title' => $linkObject->getTitle(), + 'link_type' => null, //deprecated field + 'price' => $linkObject->getPrice(), + ]; + } + + return $result; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product.php new file mode 100644 index 0000000000000..de888f0f5a629 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/../../../Magento/Downloadable/_files/product_downloadable.php'; +require __DIR__ . '/active_quote.php'; + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +/** @var $product \Magento\Catalog\Model\Product */ +$product = $productRepository->get('downloadable-product'); + +/** @var $linkCollection \Magento\Downloadable\Model\ResourceModel\Link\Collection */ +$linkCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Downloadable\Model\Link::class +)->getCollection()->addProductToFilter( + $product->getId() +)->addTitleToResult( + $product->getStoreId() +)->addPriceToResult( + $product->getStore()->getWebsiteId() +); + +/** @var $link \Magento\Downloadable\Model\Link */ +$link = $linkCollection->getFirstItem(); + +$requestInfo = new \Magento\Framework\DataObject(['qty' => 1, 'links' => [$link->getId()]]); + +/** @var $cart \Magento\Checkout\Model\Cart */ +$cart = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Checkout\Model\Cart::class); +$cart->setQuote($quote); +$cart->addProduct($product, $requestInfo); +$cart->save(); + +/** @var $objectManager \Magento\TestFramework\ObjectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product_rollback.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product_rollback.php new file mode 100644 index 0000000000000..e1ea3356938b3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote_with_downloadable_product_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +require __DIR__ . '/active_quote_rollback.php'; +require __DIR__ . '/../../Downloadable/_files/product_downloadable_rollback.php'; From 39dd6c85b319ed57195e03aae537bf910282bc05 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 29 Aug 2019 13:41:21 -0500 Subject: [PATCH 529/841] MC-19712: Full page cache issue with multiple stores --- .../Store/App/Action/Plugin/Context.php | 37 +++++++++---------- .../ContextNonDefaultStoreDirectLinkTest.php | 5 ++- .../Unit/App/Action/Plugin/ContextTest.php | 5 ++- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/Store/App/Action/Plugin/Context.php b/app/code/Magento/Store/App/Action/Plugin/Context.php index d661c9d92f3ee..1e03cc92a5898 100644 --- a/app/code/Magento/Store/App/Action/Plugin/Context.php +++ b/app/code/Magento/Store/App/Action/Plugin/Context.php @@ -11,6 +11,7 @@ use Magento\Framework\App\RequestInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\NotFoundException; +use Magento\Framework\Session\SessionManagerInterface; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Api\StoreCookieManagerInterface; use Magento\Store\Model\ScopeInterface; @@ -23,17 +24,17 @@ class Context { /** - * @var \Magento\Framework\Session\SessionManagerInterface + * @var SessionManagerInterface */ protected $session; /** - * @var \Magento\Framework\App\Http\Context + * @var HttpContext */ protected $httpContext; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; @@ -43,15 +44,15 @@ class Context protected $storeCookieManager; /** - * @param \Magento\Framework\Session\SessionManagerInterface $session - * @param \Magento\Framework\App\Http\Context $httpContext - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param SessionManagerInterface $session + * @param HttpContext $httpContext + * @param StoreManagerInterface $storeManager * @param StoreCookieManagerInterface $storeCookieManager */ public function __construct( - \Magento\Framework\Session\SessionManagerInterface $session, - \Magento\Framework\App\Http\Context $httpContext, - \Magento\Store\Model\StoreManagerInterface $storeManager, + SessionManagerInterface $session, + HttpContext $httpContext, + StoreManagerInterface $storeManager, StoreCookieManagerInterface $storeCookieManager ) { $this->session = $session; @@ -80,7 +81,7 @@ public function beforeDispatch( /** @var string|array|null $storeCode */ $storeCode = $request->getParam( - \Magento\Store\Model\StoreManagerInterface::PARAM_NAME, + StoreManagerInterface::PARAM_NAME, $this->storeCookieManager->getStoreCodeFromCookie() ); if (is_array($storeCode)) { @@ -106,13 +107,13 @@ public function beforeDispatch( * Take action in case of invalid store requested. * * @param RequestInterface $request - * @param \Throwable|null $previousException + * @param NoSuchEntityException|null $previousException * @return void * @throws NotFoundException */ private function processInvalidStoreRequested( RequestInterface $request, - \Throwable $previousException = null + NoSuchEntityException $previousException = null ) { $store = $this->storeManager->getStore(); $this->updateContext($request, $store); @@ -137,27 +138,25 @@ private function updateContext(RequestInterface $request, StoreInterface $store) { switch (true) { case $store->isUseStoreInUrl(): - $defaultStoreCode = $store->getCode(); + $defaultStore = $store; break; case ScopeInterface::SCOPE_STORE == $request->getServerValue(StoreManager::PARAM_RUN_TYPE): $defaultStoreCode = $request->getServerValue(StoreManager::PARAM_RUN_CODE); + $defaultStore = $this->storeManager->getStore($defaultStoreCode); break; default: $defaultStoreCode = $this->storeManager->getDefaultStoreView()->getCode(); + $defaultStore = $this->storeManager->getStore($defaultStoreCode); break; } $this->httpContext->setValue( StoreManagerInterface::CONTEXT_STORE, $store->getCode(), - $defaultStoreCode + $defaultStore->getCode() ); - - /** @var StoreInterface $defaultStore */ - $defaultStore = $this->storeManager->getWebsite()->getDefaultStore(); $this->httpContext->setValue( HttpContext::CONTEXT_CURRENCY, - $this->session->getCurrencyCode() - ?: $store->getDefaultCurrencyCode(), + $this->session->getCurrencyCode() ?: $store->getDefaultCurrencyCode(), $defaultStore->getDefaultCurrencyCode() ); } diff --git a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php index bb2705bff9aab..550ff86b5ace1 100644 --- a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php +++ b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php @@ -53,7 +53,10 @@ public function testCacheHitOnDirectLinkToNonDefaultStoreView( $storeCookieManager = $this->createMock(StoreCookieManagerInterface::class); $storeMock = $this->createMock(Store::class); $currentStoreMock = $this->createMock(Store::class); - $requestMock = $this->getMockBuilder(RequestInterface::class)->getMockForAbstractClass(); + $requestMock = $this->getMockBuilder(RequestInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getServerValue']) + ->getMockForAbstractClass(); $subjectMock = $this->getMockBuilder(AbstractAction::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); diff --git a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php index 616851465b49c..3ac4988648c55 100644 --- a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php +++ b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php @@ -93,7 +93,10 @@ protected function setUp() \Magento\Store\Model\Website::class, ['getDefaultStore', '__wakeup'] ); - $this->requestMock = $this->getMockBuilder(RequestInterface::class)->getMockForAbstractClass(); + $this->requestMock = $this->getMockBuilder(RequestInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getServerValue']) + ->getMockForAbstractClass(); $this->subjectMock = $this->getMockBuilder(AbstractAction::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); From c35ac36f888de3aba4c755c617d3c58a7cd9da4b Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Thu, 29 Aug 2019 19:46:54 +0000 Subject: [PATCH 530/841] Corrected code styles --- .../Customer/_files/customer_subscribe_rollback.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php index 2a84fd5556eb7..26823c7534b99 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php @@ -1,14 +1,15 @@ <?php /** -* Copyright © Magento, Inc. All rights reserved. -* See COPYING.txt for license details. -*/ + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ // TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 declare(strict_types=1); use Magento\Framework\App\Config\Storage\WriterInterface; use Magento\TestFramework\Helper\Bootstrap; + $objectManager = Bootstrap::getObjectManager(); /** @var Writer $configWriter */ $configWriter = $objectManager->create(WriterInterface::class); From a0a35c8b7fe499717148ce90d97bb6997682d705 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 29 Aug 2019 15:11:36 -0500 Subject: [PATCH 531/841] MC-19712: Full page cache issue with multiple stores --- .../ContextNonDefaultStoreDirectLinkTest.php | 165 -------- .../Unit/App/Action/Plugin/ContextTest.php | 370 ------------------ 2 files changed, 535 deletions(-) delete mode 100644 app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php delete mode 100644 app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php diff --git a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php deleted file mode 100644 index 550ff86b5ace1..0000000000000 --- a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextNonDefaultStoreDirectLinkTest.php +++ /dev/null @@ -1,165 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\Store\Test\Unit\App\Action\Plugin; - -use Magento\Framework\App\Action\AbstractAction; -use Magento\Framework\App\Http\Context as HttpContext; -use Magento\Framework\App\RequestInterface; -use Magento\Framework\Session\Generic; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Store\Api\StoreCookieManagerInterface; -use Magento\Store\App\Action\Plugin\Context; -use Magento\Store\Model\Store; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Store\Model\Website; -use PHPUnit\Framework\TestCase; - -/** - * Class ContextNonDefaultStoreDirectLinkTest - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ContextNonDefaultStoreDirectLinkTest extends TestCase -{ - const CURRENCY_SESSION = 'CNY'; - const CURRENCY_DEFAULT = 'USD'; - const CURRENCY_CURRENT_STORE = 'UAH'; - - /** - * Test for full page cache hits from new http clients if store context was specified in the URL - * - * @dataProvider cacheHitOnDirectLinkToNonDefaultStoreView - * @param string $customStore - * @param string $defaultStore - * @param string $expectedDefaultStore - * @param bool $useStoreInUrl - * @return void - */ - public function testCacheHitOnDirectLinkToNonDefaultStoreView( - string $customStore, - string $defaultStore, - string $expectedDefaultStore, - bool $useStoreInUrl - ) { - $sessionMock = $this->createPartialMock(Generic::class, ['getCurrencyCode']); - $httpContextMock = $this->createMock(HttpContext::class); - $storeManager = $this->createMock(StoreManagerInterface::class); - $storeCookieManager = $this->createMock(StoreCookieManagerInterface::class); - $storeMock = $this->createMock(Store::class); - $currentStoreMock = $this->createMock(Store::class); - $requestMock = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->setMethods(['getServerValue']) - ->getMockForAbstractClass(); - $subjectMock = $this->getMockBuilder(AbstractAction::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $httpContextMock->expects($this->once()) - ->method('getValue') - ->with(StoreManagerInterface::CONTEXT_STORE) - ->willReturn(null); - - $websiteMock = $this->createPartialMock( - Website::class, - ['getDefaultStore', '__wakeup'] - ); - - $plugin = (new ObjectManager($this))->getObject( - Context::class, - [ - 'session' => $sessionMock, - 'httpContext' => $httpContextMock, - 'storeManager' => $storeManager, - 'storeCookieManager' => $storeCookieManager, - ] - ); - - $storeManager->method('getDefaultStoreView') - ->willReturn($storeMock); - - $storeCookieManager->expects($this->once()) - ->method('getStoreCodeFromCookie') - ->willReturn('storeCookie'); - - $currentStoreMock->expects($this->any()) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_CURRENT_STORE); - - $currentStoreMock->expects($this->any()) - ->method('getCode') - ->willReturn($customStore); - - $currentStoreMock->method('isUseStoreInUrl')->willReturn($useStoreInUrl); - - $storeManager->expects($this->any()) - ->method('getWebsite') - ->willReturn($websiteMock); - - $websiteMock->expects($this->any()) - ->method('getDefaultStore') - ->willReturn($storeMock); - - $storeMock->expects($this->any()) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_DEFAULT); - - $storeMock->expects($this->any()) - ->method('getCode') - ->willReturn($defaultStore); - - $requestMock->expects($this->any()) - ->method('getParam') - ->with($this->equalTo('___store')) - ->willReturn($defaultStore); - - $storeManager->method('getStore') - ->with($defaultStore) - ->willReturn($currentStoreMock); - - $sessionMock->expects($this->any()) - ->method('getCurrencyCode') - ->willReturn(self::CURRENCY_SESSION); - - $httpContextMock->expects($this->at(1))->method( - 'setValue' - )->with(StoreManagerInterface::CONTEXT_STORE, $customStore, $expectedDefaultStore); - - $httpContextMock->expects($this->at(2))->method('setValue'); - - $plugin->beforeDispatch( - $subjectMock, - $requestMock - ); - } - - public function cacheHitOnDirectLinkToNonDefaultStoreView() - { - return [ - [ - 'custom_store', - 'default', - 'custom_store', - true, - ], - [ - 'custom_store', - 'default', - 'default', - false, - ], - [ - 'default', - 'default', - 'default', - true, - ], - ]; - } -} diff --git a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php b/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php deleted file mode 100644 index 3ac4988648c55..0000000000000 --- a/app/code/Magento/Store/Test/Unit/App/Action/Plugin/ContextTest.php +++ /dev/null @@ -1,370 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Store\Test\Unit\App\Action\Plugin; - -use Magento\Framework\App\Action\AbstractAction; -use Magento\Framework\App\Http\Context; -use Magento\Framework\App\RequestInterface; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\App\Http\Context as HttpContext; - -/** - * Class ContextPluginTest - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ContextTest extends \PHPUnit\Framework\TestCase -{ - const CURRENCY_SESSION = 'CNY'; - const CURRENCY_DEFAULT = 'USD'; - const CURRENCY_CURRENT_STORE = 'UAH'; - - /** - * @var \Magento\Store\App\Action\Plugin\Context - */ - protected $plugin; - - /** - * @var \Magento\Framework\Session\SessionManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var HttpContext|\PHPUnit_Framework_MockObject_MockObject - */ - protected $httpContextMock; - - /** - * @var \Magento\Store\Model\StoreManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $storeManager; - - /** - * @var \Magento\Store\Api\StoreCookieManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $storeCookieManager; - - /** - * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject - */ - protected $storeMock; - - /** - * @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject - */ - protected $currentStoreMock; - - /** - * @var \Magento\Store\Model\Website|\PHPUnit_Framework_MockObject_MockObject - */ - protected $websiteMock; - - /** - * @var AbstractAction|\PHPUnit_Framework_MockObject_MockObject - */ - protected $subjectMock; - - /** - * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * Set up - */ - protected function setUp() - { - $this->sessionMock = $this->createPartialMock(\Magento\Framework\Session\Generic::class, ['getCurrencyCode']); - $this->httpContextMock = $this->createMock(HttpContext::class); - $this->httpContextMock->expects($this->once()) - ->method('getValue') - ->with(StoreManagerInterface::CONTEXT_STORE) - ->willReturn(null); - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->storeCookieManager = $this->createMock(\Magento\Store\Api\StoreCookieManagerInterface::class); - $this->storeMock = $this->createMock(\Magento\Store\Model\Store::class); - $this->currentStoreMock = $this->createMock(\Magento\Store\Model\Store::class); - $this->websiteMock = $this->createPartialMock( - \Magento\Store\Model\Website::class, - ['getDefaultStore', '__wakeup'] - ); - $this->requestMock = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->setMethods(['getServerValue']) - ->getMockForAbstractClass(); - $this->subjectMock = $this->getMockBuilder(AbstractAction::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - - $this->plugin = (new ObjectManager($this))->getObject( - \Magento\Store\App\Action\Plugin\Context::class, - [ - 'session' => $this->sessionMock, - 'httpContext' => $this->httpContextMock, - 'storeManager' => $this->storeManager, - 'storeCookieManager' => $this->storeCookieManager, - ] - ); - - $this->storeManager->method('getDefaultStoreView') - ->willReturn($this->storeMock); - $this->storeCookieManager->expects($this->once()) - ->method('getStoreCodeFromCookie') - ->willReturn('storeCookie'); - $this->currentStoreMock->expects($this->any()) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_CURRENT_STORE); - } - - public function testBeforeDispatchCurrencyFromSession() - { - $this->storeManager->expects($this->once()) - ->method('getWebsite') - ->willReturn($this->websiteMock); - $this->websiteMock->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($this->storeMock); - - $this->storeMock->expects($this->once()) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_DEFAULT); - - $this->storeMock->expects($this->once()) - ->method('getCode') - ->willReturn('default'); - $this->currentStoreMock->expects($this->once()) - ->method('getCode') - ->willReturn('custom_store'); - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with($this->equalTo('___store')) - ->willReturn('default'); - - $this->storeManager->method('getStore') - ->with('default') - ->willReturn($this->currentStoreMock); - - $this->sessionMock->expects($this->any()) - ->method('getCurrencyCode') - ->willReturn(self::CURRENCY_SESSION); - - $this->httpContextMock->expects($this->at(1)) - ->method('setValue') - ->with( - StoreManagerInterface::CONTEXT_STORE, - 'custom_store', - 'default' - ); - // Make sure that current currency is taken from session if available. - $this->httpContextMock->expects($this->at(2)) - ->method('setValue') - ->with( - Context::CONTEXT_CURRENCY, - self::CURRENCY_SESSION, - self::CURRENCY_DEFAULT - ); - - $this->plugin->beforeDispatch( - $this->subjectMock, - $this->requestMock - ); - } - - public function testDispatchCurrentStoreCurrency() - { - $this->storeManager->expects($this->once()) - ->method('getWebsite') - ->willReturn($this->websiteMock); - $this->websiteMock->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($this->storeMock); - - $this->storeMock->expects($this->once()) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_DEFAULT); - - $this->storeMock->expects($this->once()) - ->method('getCode') - ->willReturn('default'); - $this->currentStoreMock->expects($this->once()) - ->method('getCode') - ->willReturn('custom_store'); - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with($this->equalTo('___store')) - ->willReturn('default'); - - $this->storeManager->method('getStore') - ->with('default') - ->willReturn($this->currentStoreMock); - - $this->httpContextMock->expects($this->at(1)) - ->method('setValue') - ->with( - StoreManagerInterface::CONTEXT_STORE, - 'custom_store', - 'default' - ); - // Make sure that current currency is taken from current store - //if no value is provided in session. - $this->httpContextMock->expects($this->at(2)) - ->method('setValue') - ->with( - Context::CONTEXT_CURRENCY, - self::CURRENCY_CURRENT_STORE, - self::CURRENCY_DEFAULT - ); - - $this->plugin->beforeDispatch( - $this->subjectMock, - $this->requestMock - ); - } - - public function testDispatchStoreParameterIsArray() - { - $this->storeManager->expects($this->once()) - ->method('getWebsite') - ->willReturn($this->websiteMock); - $this->websiteMock->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($this->storeMock); - - $this->storeMock->expects($this->once()) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_DEFAULT); - - $this->storeMock->expects($this->once()) - ->method('getCode') - ->willReturn('default'); - $this->currentStoreMock->expects($this->once()) - ->method('getCode') - ->willReturn('custom_store'); - - $store = [ - '_data' => [ - 'code' => 500, - ] - ]; - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with($this->equalTo('___store')) - ->willReturn($store); - - $this->storeManager->expects($this->once()) - ->method('getStore') - ->with('500') - ->willReturn($this->currentStoreMock); - - $this->httpContextMock->expects($this->at(1)) - ->method('setValue') - ->with( - StoreManagerInterface::CONTEXT_STORE, - 'custom_store', - 'default' - ); - //Make sure that current currency is taken from current store - //if no value is provided in session. - $this->httpContextMock->expects($this->at(2)) - ->method('setValue') - ->with( - Context::CONTEXT_CURRENCY, - self::CURRENCY_CURRENT_STORE, - self::CURRENCY_DEFAULT - ); - - $this->plugin->beforeDispatch( - $this->subjectMock, - $this->requestMock - ); - } - - /** - * @expectedException \Magento\Framework\Exception\NotFoundException - */ - public function testDispatchStoreParameterIsInvalidArray() - { - $this->storeManager->expects($this->once()) - ->method('getWebsite') - ->willReturn($this->websiteMock); - $this->websiteMock->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($this->storeMock); - $this->storeMock->expects($this->exactly(2)) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_DEFAULT); - - $this->storeMock->expects($this->exactly(2)) - ->method('getCode') - ->willReturn('default'); - $this->currentStoreMock->expects($this->never()) - ->method('getCode') - ->willReturn('custom_store'); - - $store = [ - 'some' => [ - 'code' => 500, - ] - ]; - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with($this->equalTo('___store')) - ->willReturn($store); - $this->storeManager->expects($this->once()) - ->method('getStore') - ->with() - ->willReturn($this->storeMock); - $this->plugin->beforeDispatch( - $this->subjectMock, - $this->requestMock - ); - } - - /** - * @return void - * @expectedException \Magento\Framework\Exception\NotFoundException - */ - public function testDispatchNonExistingStore() - { - $storeId = 'NonExisting'; - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('___store') - ->willReturn($storeId); - $this->storeManager->expects($this->at(0)) - ->method('getStore') - ->with($storeId) - ->willThrowException(new NoSuchEntityException()); - $this->storeManager->expects($this->at(1)) - ->method('getStore') - ->with() - ->willReturn($this->storeMock); - $this->storeManager->expects($this->once()) - ->method('getWebsite') - ->willReturn($this->websiteMock); - $this->websiteMock->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($this->storeMock); - $this->storeMock->expects($this->exactly(2)) - ->method('getDefaultCurrencyCode') - ->willReturn(self::CURRENCY_DEFAULT); - - $this->storeMock->expects($this->exactly(2)) - ->method('getCode') - ->willReturn('default'); - $this->currentStoreMock->expects($this->never()) - ->method('getCode') - ->willReturn('custom_store'); - - $this->plugin->beforeDispatch($this->subjectMock, $this->requestMock); - } -} From 87cd878883728613f2fa11f592990e4858141363 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Thu, 29 Aug 2019 15:35:34 -0500 Subject: [PATCH 532/841] Added functionality for throwing error when zero is set as category id --- .../Model/Resolver/CategoryTree.php | 2 +- .../Magento/GraphQl/Catalog/CategoryTest.php | 28 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index 74bc1a459aa50..aefe7b97b8a3f 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -64,7 +64,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value return $value[$field->getName()]; } - $rootCategoryId = !empty($args['id']) ? (int)$args['id'] : + $rootCategoryId = isset($args['id']) ? (int)$args['id'] : (int)$context->getExtensionAttributes()->getStore()->getRootCategoryId(); $this->checkCategoryIsActive->execute($rootCategoryId); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index fdd19cdfbb04a..bc55d81b455ff 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -8,14 +8,14 @@ namespace Magento\GraphQl\Catalog; use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\CategoryRepository; use Magento\Catalog\Model\ResourceModel\Category\Collection as CategoryCollection; use Magento\Framework\DataObject; +use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQl\ResponseContainsErrorsException; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\TestFramework\ObjectManager; /** * Test loading of category tree @@ -261,6 +261,25 @@ public function testGetDisabledCategory() $this->graphQlQuery($query); } + /** + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @expectedException \Exception + * @expectedExceptionMessage Category doesn't exist + */ + public function testGetCategoryIdZero() + { + $categoryId = 0; + $query = <<<QUERY +{ + category(id: {$categoryId}) { + id + name + } +} +QUERY; + $this->graphQlQuery($query); + } + public function testNonExistentCategoryWithProductCount() { $query = <<<QUERY @@ -493,8 +512,7 @@ private function assertBaseFields($product, $actualResponse) ['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()], ['response_field' => 'created_at', 'expected_value' => $product->getCreatedAt()], ['response_field' => 'name', 'expected_value' => $product->getName()], - ['response_field' => 'price', 'expected_value' => - [ + ['response_field' => 'price', 'expected_value' => [ 'minimalPrice' => [ 'amount' => [ 'value' => $product->getPrice(), From 4760f86ba4c8ad2f24f6db077a7998359574e5eb Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 29 Aug 2019 15:56:49 -0500 Subject: [PATCH 533/841] MC-18512: Dynamically inject all searchable custom attributes for product filtering --- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index d29a24428ae12..8e9471c77dc6b 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -7,7 +7,7 @@ type Query { filter: ProductAttributeFilterInput @doc(description: "Identifies which product attributes to search for and return."), pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."), currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), - sort: ProductAttributeSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") + sort: ProductAttributeSortInput @doc(description: "Specifies which attributes to sort on, and whether to return the results in ascending or descending order.") ): Products @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Products") @doc(description: "The products query searches for products that match the criteria specified in the search and filter attributes.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") category ( @@ -221,7 +221,7 @@ interface CategoryInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model products( pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."), currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1."), - sort: ProductAttributeSortInput @doc(description: "Specifies which attribute to sort on, and whether to return the results in ascending or descending order.") + sort: ProductAttributeSortInput @doc(description: "Specifies which attributes to sort on, and whether to return the results in ascending or descending order.") ): CategoryProducts @doc(description: "The list of products assigned to the category.") @cache(cacheIdentity: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\Identity") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Products") breadcrumbs: [Breadcrumb] @doc(description: "Breadcrumbs, parent categories info.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Category\\Breadcrumbs") } @@ -338,7 +338,7 @@ type ProductMediaGalleryEntriesVideoContent @doc(description: "ProductMediaGalle video_metadata: String @doc(description: "Optional data about the video.") } -input ProductSortInput @deprecated(reason: "Attributes used in this input are hardcoded and some of them are not searcheable. Use @ProductAttributeSortInput instead") @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") { +input ProductSortInput @deprecated(reason: "The attributes used in this input are hard-coded, and some of them are not sortable. Use @ProductAttributeSortInput instead") @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") { name: SortEnum @doc(description: "The product name. Customers use this name to identify the product.") sku: SortEnum @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: SortEnum @doc(description: "Detailed information about the product. The value can include simple HTML tags.") From ab3b7d8d4ea35e5728a20a6916d00b25093c937a Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 29 Aug 2019 16:36:54 -0500 Subject: [PATCH 534/841] MC-19702: Add input_type to customAttributeMetadata query - update test --- .../Catalog/ProductAttributeTypeTest.php | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php index 063da7c11bf7f..a34d5e21704af 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeTypeTest.php @@ -50,7 +50,8 @@ public function testAttributeTypeResolver() { attribute_code attribute_type - entity_type + entity_type + input_type } } } @@ -71,7 +72,8 @@ public function testAttributeTypeResolver() \Magento\Catalog\Api\Data\ProductInterface::class ]; $attributeTypes = ['String', 'Int', 'Float','Boolean', 'Float']; - $this->assertAttributeType($attributeTypes, $expectedAttributeCodes, $entityType, $response); + $inputTypes = ['textarea', 'select', 'price', 'boolean', 'price']; + $this->assertAttributeType($attributeTypes, $expectedAttributeCodes, $entityType, $inputTypes, $response); } /** @@ -121,7 +123,8 @@ public function testComplexAttributeTypeResolver() { attribute_code attribute_type - entity_type + entity_type + input_type } } } @@ -154,7 +157,16 @@ public function testComplexAttributeTypeResolver() 'CustomerDataRegionInterface', 'ProductMediaGallery' ]; - $this->assertAttributeType($attributeTypes, $expectedAttributeCodes, $entityTypes, $response); + $inputTypes = [ + 'select', + 'multiselect', + 'select', + 'select', + 'text', + 'text', + 'gallery' + ]; + $this->assertAttributeType($attributeTypes, $expectedAttributeCodes, $entityTypes, $inputTypes, $response); } /** @@ -213,11 +225,17 @@ public function testUnDefinedAttributeType() * @param array $attributeTypes * @param array $expectedAttributeCodes * @param array $entityTypes + * @param array $inputTypes * @param array $actualResponse * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - private function assertAttributeType($attributeTypes, $expectedAttributeCodes, $entityTypes, $actualResponse) - { + private function assertAttributeType( + $attributeTypes, + $expectedAttributeCodes, + $entityTypes, + $inputTypes, + $actualResponse + ) { $attributeMetaDataItems = array_map(null, $actualResponse['customAttributeMetadata']['items'], $attributeTypes); foreach ($attributeMetaDataItems as $itemIndex => $itemArray) { @@ -225,8 +243,9 @@ private function assertAttributeType($attributeTypes, $expectedAttributeCodes, $ $attributeMetaDataItems[$itemIndex][0], [ "attribute_code" => $expectedAttributeCodes[$itemIndex], - "attribute_type" =>$attributeTypes[$itemIndex], - "entity_type" => $entityTypes[$itemIndex] + "attribute_type" => $attributeTypes[$itemIndex], + "entity_type" => $entityTypes[$itemIndex], + "input_type" => $inputTypes[$itemIndex] ] ); } From 91f8f886b47578956a6059c8b4273e5800fc213c Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Thu, 29 Aug 2019 16:43:06 -0500 Subject: [PATCH 535/841] adding backward compatibility in constructor --- app/code/Magento/Wishlist/Model/Wishlist.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/Wishlist.php b/app/code/Magento/Wishlist/Model/Wishlist.php index 0d9c74edb3d4c..827607e72f807 100644 --- a/app/code/Magento/Wishlist/Model/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Wishlist.php @@ -175,11 +175,11 @@ class Wishlist extends AbstractModel implements IdentityInterface * @param Random $mathRandom * @param DateTime $dateTime * @param ProductRepositoryInterface $productRepository - * @param StockItemRepository $stockItemRepository - * @param ScopeConfigInterface|null $scopeConfig * @param bool $useCurrentWebsite * @param array $data * @param Json|null $serializer + * @param StockItemRepository|null $stockItemRepository + * @param ScopeConfigInterface|null $scopeConfig * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -197,11 +197,11 @@ public function __construct( Random $mathRandom, DateTime $dateTime, ProductRepositoryInterface $productRepository, - StockItemRepository $stockItemRepository, - ScopeConfigInterface $scopeConfig = null, $useCurrentWebsite = true, array $data = [], - Json $serializer = null + Json $serializer = null, + StockItemRepository $stockItemRepository = null, + ScopeConfigInterface $scopeConfig = null ) { $this->_useCurrentWebsite = $useCurrentWebsite; $this->_catalogProduct = $catalogProduct; @@ -216,7 +216,9 @@ public function __construct( $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->productRepository = $productRepository; - $this->stockItemRepository = $stockItemRepository; + $this->stockItemRepository = $stockItemRepository ?: ObjectManager::getInstance()->get( + StockItemRepository::class + ); $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } From bd313ebd5a78afa7718e19f350b6aa836318228e Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Wed, 28 Aug 2019 13:31:22 -0500 Subject: [PATCH 536/841] MC-19683: Wrong "As low as" price on catalog page - Fix wrong minimum price for configurable product on catalog page --- .../AdminProductGridActionGroup.xml | 27 ++- ...CheckProductPriceInCategoryActionGroup.xml | 10 ++ .../Test/Mftf/Data/ProductAttributeData.xml | 8 + .../AdminConfigurableProductActionGroup.xml | 74 +++++++- .../AddSwatchToProductActionGroup.xml | 36 ++++ .../StorefrontProductActionGroup.xml | 8 + .../Test/Mftf/Data/SwatchAttributeData.xml | 5 + ...figurableProductSwatchMinimumPriceTest.xml | 167 ++++++++++++++++++ .../view/frontend/web/js/swatch-renderer.js | 62 ++++--- 9 files changed, 362 insertions(+), 35 deletions(-) create mode 100644 app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableProductSwatchMinimumPriceTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index d0426671104c4..6a260bbf22522 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -13,7 +13,7 @@ <annotations> <description>Sets the Admin Products grid view to the 'Default View'.</description> </annotations> - + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> <click selector="{{AdminProductGridFilterSection.viewDropdown}}" stepKey="openViewBookmarksTab"/> <click selector="{{AdminProductGridFilterSection.viewBookmark('Default View')}}" stepKey="resetToDefaultGridView"/> @@ -120,7 +120,7 @@ <annotations> <description>Filters the Admin Products grid by the 'Enabled' Status. PLEASE NOTE: The Filter is Hardcoded.</description> </annotations> - + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> <selectOption selector="{{AdminProductGridFilterSection.statusFilter}}" userInput="Enabled" stepKey="selectEnabledStatusFilter"/> @@ -286,6 +286,27 @@ <see selector="{{AdminProductGridSection.productGridCell('1', 'Name')}}" userInput="{{name}}" stepKey="seeProductNameInGrid" after="clickApplyFilters"/> </actionGroup> + <actionGroup name="deleteProductsByKeyword"> + <annotations> + <description>Delete products by keyword</description> + </annotations> + <arguments> + <argument name="keyword" type="string"/> + </arguments> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="{{keyword}}" stepKey="fillKeywordField"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="keywordSearchButton"/> + <click selector="{{AdminProductGridSection.multicheckDropdown}}" stepKey="openMulticheckDropdown"/> + <click selector="{{AdminProductGridSection.multicheckOption('Select All')}}" stepKey="selectAllProductInFilteredGrid"/> + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Delete')}}" stepKey="clickDeleteAction"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForConfirmModal"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmProductDelete"/> + <see selector="{{AdminMessagesSection.success}}" userInput="record(s) have been deleted." stepKey="seeSuccessMessage"/> + </actionGroup> + <!--Open product for edit by clicking row X and column Y in product grid--> <actionGroup name="openProducForEditByClickingRowXColumnYInProductGrid"> <annotations> @@ -314,7 +335,7 @@ <annotations> <description>Filters the ID column in Ascending Order.</description> </annotations> - + <conditionalClick selector="{{AdminProductGridTableHeaderSection.id('descend')}}" dependentSelector="{{AdminProductGridTableHeaderSection.id('ascend')}}" visible="false" stepKey="sortById"/> <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml index bdab0572324b3..ac33727564505 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCheckProductPriceInCategoryActionGroup.xml @@ -17,4 +17,14 @@ <remove keyForRemoval="AssertProductPrice"/> <see userInput="{{product.price}}" selector="{{StorefrontCategoryProductSection.ProductPriceByName(product.name)}}" stepKey="AssertProductPrice"/> </actionGroup> + <actionGroup name="StorefrontAssertProductPriceOnCategoryPageActionGroup" extends="StorefrontAssertProductPriceOnProductPageActionGroup"> + <annotations> + <description>Validate that the provided Product Price is correct on category page.</description> + </annotations> + <arguments> + <argument name="productName" type="string"/> + </arguments> + + <see userInput="{{productPrice}}" selector="{{StorefrontCategoryProductSection.ProductPriceByName(productName)}}" stepKey="seeProductPrice"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index 6bbca45741c75..2deec6b8c1f8e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -368,4 +368,12 @@ <data key="frontend_input">swatch_visual</data> <data key="attribute_code" unique="suffix">visual_swatch</data> </entity> + <entity name="ProductColorAttribute" type="ProductAttribute"> + <data key="frontend_label">Color</data> + <data key="attribute_code" unique="suffix">color_attr</data> + </entity> + <entity name="ProductSizeAttribute" type="ProductAttribute"> + <data key="frontend_label">Size</data> + <data key="attribute_code" unique="suffix">size_attr</data> + </entity> </entities> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml index a0a3a551c3d93..488667a4585bb 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml @@ -16,7 +16,7 @@ <arguments> <argument name="product" defaultValue="_defaultProduct"/> </arguments> - + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> <waitForPageLoad stepKey="waitForPageLoadInitial"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> @@ -108,6 +108,65 @@ <seeInTitle userInput="{{product.name}}" stepKey="seeProductNameInTitle"/> </actionGroup> + <actionGroup name="createConfigurableProductWithTwoAttributes" extends="createConfigurableProduct"> + <annotations> + <description>Goes to the Admin Product grid page. Creates a Configurable Product with 2 product attributes.</description> + </annotations> + <arguments> + <argument name="attribute1" defaultValue="ProductColorAttribute"/> + <argument name="attribute2" defaultValue="ProductSizeAttribute"/> + </arguments> + <remove keyForRemoval="clickOnNewAttribute"/> + <remove keyForRemoval="waitForIFrame"/> + <remove keyForRemoval="switchToNewAttributeIFrame"/> + <remove keyForRemoval="fillDefaultLabel"/> + <remove keyForRemoval="clickOnNewAttributePanel"/> + <remove keyForRemoval="waitForSaveAttribute"/> + <remove keyForRemoval="switchOutOfIFrame"/> + <remove keyForRemoval="waitForFilters"/> + <remove keyForRemoval="clickOnFilters"/> + <remove keyForRemoval="fillFilterAttributeCodeField"/> + <remove keyForRemoval="clickApplyFiltersButton"/> + <remove keyForRemoval="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.attributeCheckbox(attribute1.attribute_code)}}" stepKey="selectAttribute1" after="clickOnCreateConfigurations"/> + <click selector="{{AdminCreateProductConfigurationsPanel.attributeCheckbox(attribute2.attribute_code)}}" stepKey="selectAttribute2" after="selectAttribute1"/> + <remove keyForRemoval="waitCreateNewValueAppears"/> + <remove keyForRemoval="clickOnCreateNewValue1"/> + <remove keyForRemoval="fillFieldForNewAttribute1"/> + <remove keyForRemoval="clickOnSaveNewAttribute1"/> + <remove keyForRemoval="clickOnCreateNewValue2"/> + <remove keyForRemoval="fillFieldForNewAttribute2"/> + <remove keyForRemoval="clickOnSaveNewAttribute2"/> + <remove keyForRemoval="clickOnCreateNewValue3"/> + <remove keyForRemoval="fillFieldForNewAttribute3"/> + <remove keyForRemoval="clickOnSaveNewAttribute3"/> + <remove keyForRemoval="clickOnSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute(attribute1.frontend_label)}}" stepKey="selectAllOptionsOfAttribute1" before="clickOnNextButton2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute(attribute2.frontend_label)}}" stepKey="selectAllOptionsOfAttribute2" before="clickOnNextButton2"/> + <remove keyForRemoval="applyUniquePricesByAttributeToEachSku"/> + <remove keyForRemoval="clickOnApplyUniquePricesByAttributeToEachSku"/> + <remove keyForRemoval="selectAttributes"/> + <remove keyForRemoval="fillAttributePrice1"/> + <remove keyForRemoval="fillAttributePrice2"/> + <remove keyForRemoval="fillAttributePrice3"/> + <remove keyForRemoval="clickOnSaveButton2"/> + <remove keyForRemoval="clickOnConfirmInPopup"/> + <remove keyForRemoval="seeSaveProductMessage"/> + <remove keyForRemoval="seeProductNameInTitle"/> + </actionGroup> + <actionGroup name="saveConfigurableProduct"> + <annotations> + <description>Save configurable product</description> + </annotations> + <arguments> + <argument name="product" defaultValue="_defaultProduct"/> + </arguments> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage"/> + <seeInTitle userInput="{{product.name}}" stepKey="seeProductNameInTitle"/> + </actionGroup> + <actionGroup name="generateConfigurationsByAttributeCode"> <annotations> <description>Generates the Product Configurations for the provided Attribute Code on the Configurable Product creation/edit page.</description> @@ -296,12 +355,21 @@ <fillField userInput="{{firstOption.weight}}" selector="{{AdminProductFormConfigurationsSection.confProductWeightCell(firstOption.name)}}" stepKey="fillFieldWeightForFirstAttributeOption"/> <fillField userInput="{{secondOption.weight}}" selector="{{AdminProductFormConfigurationsSection.confProductWeightCell(secondOption.name)}}" stepKey="fillFieldWeightForSecondAttributeOption"/> </actionGroup> - + <actionGroup name="changeConfigurableProductChildProductPrice"> + <annotations> + <description>Change the price of a configurable child product in the grid under configurations.</description> + </annotations> + <arguments> + <argument name="productAttributes" type="string"/> + <argument name="productPrice" type="string"/> + </arguments> + <fillField userInput="{{productPrice}}" selector="{{AdminProductFormConfigurationsSection.confProductPriceCell(productAttributes)}}" stepKey="fillPriceForConfigurableProductAttributeOption"/> + </actionGroup> <actionGroup name="changeProductConfigurationsInGridExceptSku" extends="changeProductConfigurationsInGrid"> <annotations> <description>EXTENDS: changeProductConfigurationsInGrid. Removes 'fillFieldSkuForFirstAttributeOption' and 'fillFieldSkuForSecondAttributeOption'.</description> </annotations> - + <remove keyForRemoval="fillFieldSkuForFirstAttributeOption"/> <remove keyForRemoval="fillFieldSkuForSecondAttributeOption"/> </actionGroup> diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml index cd8470fe47fde..8b95b86065b7d 100644 --- a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml @@ -84,4 +84,40 @@ <selectOption selector="{{AdminNewAttributePanel.visibleOnCatalogPagesOnStorefront}}" stepKey="switchOnVisibleOnCatalogPagesOnStorefront" userInput="Yes" after="selectUseInLayer"/> <selectOption selector="{{AdminNewAttributePanel.useInProductListing}}" stepKey="switchOnUsedInProductListing" userInput="Yes" after="switchOnVisibleOnCatalogPagesOnStorefront"/> </actionGroup> + + <actionGroup name="AddTextSwatchToProductActionGroup"> + <annotations> + <description>Add text swatch property attribute.</description> + </annotations> + <arguments> + <argument name="attributeName" defaultValue="{{textSwatchAttribute.default_label}}" type="string"/> + <argument name="attributeCode" defaultValue="{{textSwatchAttribute.attribute_code}}" type="string"/> + <argument name="option1" defaultValue="textSwatchOption1" type="string"/> + <argument name="option2" defaultValue="textSwatchOption2" type="string"/> + <argument name="option3" defaultValue="textSwatchOption3" type="string"/> + <argument name="usedInProductListing" defaultValue="No" type="string"/> + </arguments> + + <amOnPage url="{{ProductAttributePage.url}}" stepKey="goToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForNewProductAttributePage"/> + <fillField selector="{{AttributePropertiesSection.DefaultLabel}}" userInput="{{attributeName}}" stepKey="fillDefaultLabel"/> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="{{textSwatchAttribute.input_type}}" stepKey="selectInputType"/> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch1"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('0')}}" userInput="{{option1}}" stepKey="fillSwatch1"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('0')}}" userInput="{{option1}}" stepKey="fillSwatch1Description"/> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch2"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('1')}}" userInput="{{option2}}" stepKey="fillSwatch2"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('1')}}" userInput="{{option2}}" stepKey="fillSwatch2Description"/> + <click selector="{{AdminManageSwatchSection.addSwatchText}}" stepKey="clickAddSwatch3"/> + <fillField selector="{{AdminManageSwatchSection.swatchTextByIndex('2')}}" userInput="{{option3}}" stepKey="fillSwatch3"/> + <fillField selector="{{AdminManageSwatchSection.swatchAdminDescriptionByIndex('2')}}" userInput="{{option3}}" stepKey="fillSwatch3Description"/> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectGlobalScope"/> + <fillField selector="{{AdvancedAttributePropertiesSection.AttributeCode}}" userInput="{{attributeCode}}" stepKey="fillAttributeCodeField"/> + <scrollToTopOfPage stepKey="scrollToTabs"/> + <click selector="{{StorefrontPropertiesSection.StoreFrontPropertiesTab}}" stepKey="clickStorefrontPropertiesTab"/> + <waitForElementVisible selector="{{AdvancedAttributePropertiesSection.UseInProductListing}}" stepKey="waitForTabSwitch"/> + <selectOption selector="{{AdvancedAttributePropertiesSection.UseInProductListing}}" userInput="{{usedInProductListing}}" stepKey="useInProductListing"/> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSave"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml index a67f9d8999b59..6ca0c220778d6 100644 --- a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml @@ -15,6 +15,14 @@ </arguments> <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel(optionName)}}" stepKey="clickSwatchOption"/> </actionGroup> + <actionGroup name="StorefrontAssertSwatchOptionPrice"> + <arguments> + <argument name="optionName" type="string"/> + <argument name="optionPrice" type="string"/> + </arguments> + <click selector="{{StorefrontProductInfoMainSection.swatchOptionByLabel(optionName)}}" stepKey="clickOnOption"/> + <see userInput="{{optionPrice}}" selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="seeOptionPrice"/> + </actionGroup> <!--Click a swatch option on product page and check active image--> <actionGroup name="StorefrontSelectSwatchOptionOnProductPageAndCheckImage" extends="StorefrontSelectSwatchOptionOnProductPage"> diff --git a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml index d7ee6dabce7ed..b05c9cc9e7a9a 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Data/SwatchAttributeData.xml @@ -13,4 +13,9 @@ <data key="input_type">Visual Swatch</data> <data key="attribute_code" unique="suffix">visual_swatch_attr</data> </entity> + <entity name="textSwatchAttribute" type="SwatchAttribute"> + <data key="input_type">Text Swatch</data> + <data key="default_label" unique="suffix">TextSwatchAttr</data> + <data key="attribute_code" unique="suffix">text_swatch_attr</data> + </entity> </entities> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableProductSwatchMinimumPriceTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableProductSwatchMinimumPriceTest.xml new file mode 100644 index 0000000000000..0a98e7a721c17 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontConfigurableProductSwatchMinimumPriceTest.xml @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontConfigurableProductSwatchMinimumPriceProductPageTest"> + <annotations> + <features value="Swatches"/> + <stories value="Configurable product with swatch attribute"/> + <title value="Swatch option should show the lowest price possible on product page"/> + <description value="Swatch option should show the lowest price possible on product page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-19683"/> + <group value="Swatches"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + </before> + <after> + <!-- Delete configurable product and all child products --> + <actionGroup ref="deleteProductsByKeyword" stepKey="deleteProductsByKeyword"> + <argument name="keyword" value="{{_defaultProduct.sku}}"/> + </actionGroup> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategoryAttribute"/> + <!-- Delete color attribute --> + <actionGroup ref="deleteProductAttribute" stepKey="deleteColorAttribute"> + <argument name="ProductAttribute" value="ProductColorAttribute"/> + </actionGroup> + <!-- Delete size attribute --> + <actionGroup ref="deleteProductAttribute" stepKey="deleteSizeAttribute"> + <argument name="ProductAttribute" value="ProductSizeAttribute"/> + </actionGroup> + <!-- Logout --> + <actionGroup ref="logout" stepKey="amOnLogoutPage"/> + </after> + <!--Create text swatch attribute with 3 options: Black, White and Blue--> + <actionGroup ref="AddTextSwatchToProductActionGroup" stepKey="addColorAttribute"> + <argument name="attributeName" value="{{ProductColorAttribute.frontend_label}}"/> + <argument name="attributeCode" value="{{ProductColorAttribute.attribute_code}}"/> + <argument name="option1" value="Black"/> + <argument name="option2" value="White"/> + <argument name="option3" value="Blue"/> + <argument name="usedInProductListing" value="Yes"/> + </actionGroup> + <!--Create text swatch attribute with 3 options: Small, Medium and Large--> + <actionGroup ref="AddTextSwatchToProductActionGroup" stepKey="addSizeAttribute"> + <argument name="attributeName" value="{{ProductSizeAttribute.frontend_label}}"/> + <argument name="attributeCode" value="{{ProductSizeAttribute.attribute_code}}"/> + <argument name="option1" value="Small"/> + <argument name="option2" value="Medium"/> + <argument name="option3" value="Large"/> + </actionGroup> + <!--Create configurable product with two attributes: Color and Size--> + <actionGroup ref="createConfigurableProductWithTwoAttributes" stepKey="createProduct"> + <argument name="product" value="_defaultProduct"/> + <argument name="category" value="$$createCategory$$"/> + <argument name="attribute1" value="ProductColorAttribute"/> + <argument name="attribute2" value="ProductSizeAttribute"/> + </actionGroup> + <!--Set Black-Small product price to 10--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeBlackSmallPrice"> + <argument name="productAttributes" value="Color: Black, Size: Small"/> + <argument name="productPrice" value="10"/> + </actionGroup> + <!--Set Black-Medium product price to 11--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeBlackMediumPrice"> + <argument name="productAttributes" value="Color: Black, Size: Medium"/> + <argument name="productPrice" value="11"/> + </actionGroup> + <!--Set Black-Large product price to 12--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeBlackLargePrice"> + <argument name="productAttributes" value="Color: Black, Size: Large"/> + <argument name="productPrice" value="12"/> + </actionGroup> + <!--Set White-Small product price to 14--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeWhiteSmallPrice"> + <argument name="productAttributes" value="Color: White, Size: Small"/> + <argument name="productPrice" value="14"/> + </actionGroup> + <!--Set White-Medium product price to 13--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeWhiteMediumPrice"> + <argument name="productAttributes" value="Color: White, Size: Medium"/> + <argument name="productPrice" value="13"/> + </actionGroup> + <!--Set White-Large product price to 15--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeWhiteLargePrice"> + <argument name="productAttributes" value="Color: White, Size: Large"/> + <argument name="productPrice" value="15"/> + </actionGroup> + <!--Set Blue-Small product price to 18--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeBlueSmallPrice"> + <argument name="productAttributes" value="Color: Blue, Size: Small"/> + <argument name="productPrice" value="18"/> + </actionGroup> + <!--Set Blue-Medium product price to 17--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeBlueMediumPrice"> + <argument name="productAttributes" value="Color: Blue, Size: Medium"/> + <argument name="productPrice" value="17"/> + </actionGroup> + <!--Set Blue-Large product price to 16--> + <actionGroup ref="changeConfigurableProductChildProductPrice" stepKey="changeBlueLargePrice"> + <argument name="productAttributes" value="Color: Blue, Size: Large"/> + <argument name="productPrice" value="16"/> + </actionGroup> + <!--Save configurable product--> + <actionGroup ref="saveConfigurableProduct" stepKey="saveProduct"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + + <!--Go to product page--> + <amOnPage url="{{StorefrontProductPage.url(_defaultProduct.urlKey)}}" stepKey="amOnConfigurableProductPage"/> + <waitForPageLoad stepKey="waitForConfigurableProductPage"/> + + <!--Verify that the minimum price is 10--> + <actionGroup ref="StorefrontAssertProductPriceOnProductPageActionGroup" stepKey="assertProductPrice"> + <argument name="productPrice" value="10.00"/> + </actionGroup> + + <!--Verify that Black option's minimum price is 16--> + <actionGroup ref="StorefrontAssertSwatchOptionPrice" stepKey="assertMinimumPriceForBlackOption"> + <argument name="optionName" value="Black"/> + <argument name="optionPrice" value="10.00"/> + </actionGroup> + + <!--Verify that White option's minimum price is 16--> + <actionGroup ref="StorefrontAssertSwatchOptionPrice" stepKey="assertMinimumPriceForWhiteOption"> + <argument name="optionName" value="White"/> + <argument name="optionPrice" value="13.00"/> + </actionGroup> + + <!--Verify that Blue option's minimum price is 16--> + <actionGroup ref="StorefrontAssertSwatchOptionPrice" stepKey="assertMinimumPriceForBlueOption"> + <argument name="optionName" value="Blue"/> + <argument name="optionPrice" value="16.00"/> + </actionGroup> + </test> + <test name="StorefrontConfigurableProductSwatchMinimumPriceCategoryPageTest" extends="StorefrontConfigurableProductSwatchMinimumPriceProductPageTest"> + <annotations> + <features value="Swatches"/> + <stories value="Configurable product with swatch attribute"/> + <title value="Swatch option should show the lowest price possible on category page"/> + <description value="Swatch option should show the lowest price possible on category page"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-19683"/> + <group value="Swatches"/> + </annotations> + + <!--Go to category page--> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="amOnConfigurableProductPage"/> + <waitForPageLoad stepKey="waitForConfigurableProductPage"/> + + <!--Verify that the minimum price is 10--> + <actionGroup ref="StorefrontAssertProductPriceOnCategoryPageActionGroup" stepKey="assertProductPrice"> + <argument name="productName" value="{{_defaultProduct.name}}"/> + <argument name="productPrice" value="10.00"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index d6302cff83bff..f4b4be7657b8c 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -927,19 +927,10 @@ define([ var $widget = this, $product = $widget.element.parents($widget.options.selectorProduct), $productPrice = $product.find(this.options.selectorProductPrice), - options = _.object(_.keys($widget.optionsMap), {}), - result, + result = $widget._getNewPrices(), tierPriceHtml, isShow; - $widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function () { - var attributeId = $(this).attr('attribute-id'); - - options[attributeId] = $(this).attr('option-selected'); - }); - - result = $widget.options.jsonConfig.optionPrices[_.findKey($widget.options.jsonConfig.index, options)]; - $productPrice.trigger( 'updatePrice', { @@ -951,7 +942,7 @@ define([ $product.find(this.options.slyOldPriceSelector)[isShow ? 'show' : 'hide'](); - if (typeof result != 'undefined' && result.tierPrices.length) { + if (typeof result != 'undefined' && result.tierPrices && result.tierPrices.length) { if (this.options.tierPriceTemplate) { tierPriceHtml = mageTemplate( this.options.tierPriceTemplate, @@ -985,6 +976,35 @@ define([ }.bind(this)); }, + /** + * Get new prices for selected options + * + * @returns {*} + * @private + */ + _getNewPrices: function () { + var $widget = this, + optionPriceDiff = 0, + allowedProduct = this._getAllowedProductWithMinPrice(this._CalcProducts()), + optionPrices = this.options.jsonConfig.optionPrices, + basePrice = parseFloat(this.options.jsonConfig.prices.basePrice.amount), + optionFinalPrice, + newPrices; + + if (!_.isEmpty(allowedProduct)) { + optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount); + optionPriceDiff = optionFinalPrice - basePrice; + } + + if (optionPriceDiff !== 0) { + newPrices = this.options.jsonConfig.optionPrices[allowedProduct]; + } else { + newPrices = $widget.options.jsonConfig.prices; + } + + return newPrices; + }, + /** * Get prices * @@ -994,27 +1014,11 @@ define([ * @private */ _getPrices: function (newPrices, displayPrices) { - var $widget = this, - optionPriceDiff = 0, - allowedProduct, optionPrices, basePrice, optionFinalPrice; + var $widget = this; if (_.isEmpty(newPrices)) { - allowedProduct = this._getAllowedProductWithMinPrice(this._CalcProducts()); - optionPrices = this.options.jsonConfig.optionPrices; - basePrice = parseFloat(this.options.jsonConfig.prices.basePrice.amount); - - if (!_.isEmpty(allowedProduct)) { - optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount); - optionPriceDiff = optionFinalPrice - basePrice; - } - - if (optionPriceDiff !== 0) { - newPrices = this.options.jsonConfig.optionPrices[allowedProduct]; - } else { - newPrices = $widget.options.jsonConfig.prices; - } + newPrices = $widget._getNewPrices(); } - _.each(displayPrices, function (price, code) { if (newPrices[code]) { From 239ef1494e2c25d7cdd84e82072cdf71a9f6a5b5 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Thu, 29 Aug 2019 18:22:31 -0500 Subject: [PATCH 537/841] MC-19689: Simple product disappearing in the configurable grid after qty set to 0 --- .../Model/Product/Type/Configurable.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index a849d964eaed5..77450748f7eba 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -1304,15 +1304,11 @@ private function loadUsedProducts(\Magento\Catalog\Model\Product $product, $cach { $dataFieldName = $salableOnly ? $this->usedSalableProducts : $this->_usedProducts; if (!$product->hasData($dataFieldName)) { - $usedProducts = $this->readUsedProductsCacheData($cacheKey); - if ($usedProducts === null) { - $collection = $this->getConfiguredUsedProductCollection($product, false); - if ($salableOnly) { - $collection = $this->salableProcessor->process($collection); - } - $usedProducts = array_values($collection->getItems()); - $this->saveUsedProductsCacheData($product, $usedProducts, $cacheKey); + $collection = $this->getConfiguredUsedProductCollection($product, false); + if ($salableOnly) { + $collection = $this->salableProcessor->process($collection); } + $usedProducts = array_values($collection->getItems()); $product->setData($dataFieldName, $usedProducts); } From 6753604a30cb29fcc1772bc90e639e0a2f459c3c Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Thu, 29 Aug 2019 21:53:18 -0500 Subject: [PATCH 538/841] wrong order of constructor --- app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php index cbbccf29e63b8..4d4d1a235de1d 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php @@ -206,11 +206,11 @@ protected function setUp() $this->mathRandom, $this->dateTime, $this->productRepository, - $this->stockItemRepository, - $this->scopeConfig, false, [], - $this->serializer + $this->serializer, + $this->stockItemRepository, + $this->scopeConfig ); } From 58421600483bc64e561e76613ad4043e51c85a07 Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Sun, 25 Aug 2019 14:29:13 +0300 Subject: [PATCH 539/841] magento/magento2#: Replace deprecated methods in back-end controllers of Magento/Customer - app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php - app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php - app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php - app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php - app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php - app/code/Magento/Customer/Controller/Adminhtml/Index.php - app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php --- .../Customer/Controller/Adminhtml/Index.php | 4 +- .../Adminhtml/Index/MassAssignGroup.php | 2 +- .../Controller/Adminhtml/Index/MassDelete.php | 2 +- .../Adminhtml/Index/MassSubscribe.php | 5 +- .../Adminhtml/Index/MassUnsubscribe.php | 5 +- .../Adminhtml/Index/ResetPassword.php | 6 +- .../Controller/Adminhtml/Index/Save.php | 7 +- .../Controller/Adminhtml/Locks/Unlock.php | 7 +- .../Adminhtml/Index/MassAssignGroupTest.php | 2 +- .../Adminhtml/Index/MassDeleteTest.php | 2 +- .../Adminhtml/Index/MassSubscribeTest.php | 2 +- .../Adminhtml/Index/MassUnsubscribeTest.php | 2 +- .../Adminhtml/Index/ResetPasswordTest.php | 6 +- .../Controller/Adminhtml/Index/SaveTest.php | 92 +++++++++++-------- .../Controller/Adminhtml/Locks/UnlockTest.php | 4 +- .../Adminhtml/Index/MassSubscribeTest.php | 7 +- 16 files changed, 91 insertions(+), 64 deletions(-) diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index.php b/app/code/Magento/Customer/Controller/Adminhtml/Index.php index a0317a51260da..ffae1e9f8bf1e 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index.php @@ -311,7 +311,7 @@ protected function _addSessionErrorMessages($messages) protected function actUponMultipleCustomers(callable $singleAction, $customerIds) { if (!is_array($customerIds)) { - $this->messageManager->addError(__('Please select customer(s).')); + $this->messageManager->addErrorMessage(__('Please select customer(s).')); return 0; } $customersUpdated = 0; @@ -320,7 +320,7 @@ protected function actUponMultipleCustomers(callable $singleAction, $customerIds $singleAction($customerId); $customersUpdated++; } catch (\Exception $exception) { - $this->messageManager->addError($exception->getMessage()); + $this->messageManager->addErrorMessage($exception->getMessage()); } } return $customersUpdated; diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php index 5a9c52bf9b1c0..f55c81da7e0b9 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php @@ -60,7 +60,7 @@ protected function massAction(AbstractCollection $collection) } if ($customersUpdated) { - $this->messageManager->addSuccess(__('A total of %1 record(s) were updated.', $customersUpdated)); + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were updated.', $customersUpdated)); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php index edaeea6a15eb2..85286573bc5e7 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php @@ -58,7 +58,7 @@ protected function massAction(AbstractCollection $collection) } if ($customersDeleted) { - $this->messageManager->addSuccess(__('A total of %1 record(s) were deleted.', $customersDeleted)); + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were deleted.', $customersDeleted)); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php index 25c56ac60c14b..29a66bf1ff933 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassSubscribe.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Ui\Component\MassAction\Filter; use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory; use Magento\Customer\Api\CustomerRepositoryInterface; @@ -16,7 +17,7 @@ /** * Class MassSubscribe */ -class MassSubscribe extends AbstractMassAction +class MassSubscribe extends AbstractMassAction implements HttpPostActionInterface { /** * @var CustomerRepositoryInterface @@ -64,7 +65,7 @@ protected function massAction(AbstractCollection $collection) } if ($customersUpdated) { - $this->messageManager->addSuccess(__('A total of %1 record(s) were updated.', $customersUpdated)); + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were updated.', $customersUpdated)); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php index 4b40722ba9ab2..fddf18489b9a5 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassUnsubscribe.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Newsletter\Model\SubscriberFactory; @@ -16,7 +17,7 @@ /** * Class MassUnsubscribe */ -class MassUnsubscribe extends AbstractMassAction +class MassUnsubscribe extends AbstractMassAction implements HttpPostActionInterface { /** * @var CustomerRepositoryInterface @@ -64,7 +65,7 @@ protected function massAction(AbstractCollection $collection) } if ($customersUpdated) { - $this->messageManager->addSuccess(__('A total of %1 record(s) were updated.', $customersUpdated)); + $this->messageManager->addSuccessMessage(__('A total of %1 record(s) were updated.', $customersUpdated)); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php index 1e4fa91cbf899..3b9370c32bf6d 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php @@ -44,7 +44,9 @@ public function execute() \Magento\Customer\Model\AccountManagement::EMAIL_REMINDER, $customer->getWebsiteId() ); - $this->messageManager->addSuccess(__('The customer will receive an email with a link to reset password.')); + $this->messageManager->addSuccessMessage( + __('The customer will receive an email with a link to reset password.') + ); } catch (NoSuchEntityException $exception) { $resultRedirect->setPath('customer/index'); return $resultRedirect; @@ -57,7 +59,7 @@ public function execute() } catch (SecurityViolationException $exception) { $this->messageManager->addErrorMessage($exception->getMessage()); } catch (\Exception $exception) { - $this->messageManager->addException( + $this->messageManager->addExceptionMessage( $exception, __('Something went wrong while resetting customer password.') ); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 38ed688a835bc..3ee33af9ec073 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -354,7 +354,7 @@ public function execute() $this->_getSession()->unsCustomerFormData(); // Done Saving customer, finish save action $this->_coreRegistry->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId); - $this->messageManager->addSuccess(__('You saved the customer.')); + $this->messageManager->addSuccessMessage(__('You saved the customer.')); $returnToEdit = (bool)$this->getRequest()->getParam('back', false); } catch (\Magento\Framework\Validator\Exception $exception) { $messages = $exception->getMessages(); @@ -378,7 +378,10 @@ public function execute() $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData()); $returnToEdit = true; } catch (\Exception $exception) { - $this->messageManager->addException($exception, __('Something went wrong while saving the customer.')); + $this->messageManager->addExceptionMessage( + $exception, + __('Something went wrong while saving the customer.') + ); $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData()); $returnToEdit = true; } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php b/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php index 1fd06a3182948..32299de777c31 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Locks/Unlock.php @@ -7,13 +7,14 @@ namespace Magento\Customer\Controller\Adminhtml\Locks; use Magento\Customer\Model\AuthenticationInterface; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action; /** * Unlock Customer Controller */ -class Unlock extends \Magento\Backend\App\Action +class Unlock extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -55,10 +56,10 @@ public function execute() // unlock customer if ($customerId) { $this->authentication->unlock($customerId); - $this->getMessageManager()->addSuccess(__('Customer has been unlocked successfully.')); + $this->getMessageManager()->addSuccessMessage(__('Customer has been unlocked successfully.')); } } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php index 10144bdc318c1..cb5ff88ab704a 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php @@ -170,7 +170,7 @@ public function testExecute() ->willReturnMap([[10, $customerMock], [11, $customerMock], [12, $customerMock]]); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) were updated.', count($customersIds))); $this->resultRedirectMock->expects($this->any()) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php index 190ff2c06618f..1f39e6306b996 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php @@ -155,7 +155,7 @@ public function testExecute() ->willReturnMap([[10, true], [11, true], [12, true]]); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) were deleted.', count($customersIds))); $this->resultRedirectMock->expects($this->any()) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php index daf9c64fe7b7b..90bff0b61bcbf 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php @@ -171,7 +171,7 @@ public function testExecute() ->willReturnMap([[10, true], [11, true], [12, true]]); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) were updated.', count($customersIds))); $this->resultRedirectMock->expects($this->any()) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php index 05624661a2de4..1bffa836f5034 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php @@ -171,7 +171,7 @@ public function testExecute() ->willReturnMap([[10, true], [11, true], [12, true]]); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('A total of %1 record(s) were updated.', count($customersIds))); $this->resultRedirectMock->expects($this->any()) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php index 66e5b57eaa424..67ac60e6b9057 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php @@ -141,7 +141,7 @@ protected function setUp() $this->messageManager = $this->getMockBuilder( \Magento\Framework\Message\Manager::class )->disableOriginalConstructor()->setMethods( - ['addSuccess', 'addMessage', 'addException', 'addErrorMessage'] + ['addSuccessMessage', 'addMessage', 'addExceptionMessage', 'addErrorMessage'] )->getMock(); $this->resultRedirectFactoryMock = $this->getMockBuilder( @@ -442,7 +442,7 @@ public function testResetPasswordActionException() $this->messageManager->expects( $this->once() )->method( - 'addException' + 'addExceptionMessage' )->with( $this->equalTo($exception), $this->equalTo('Something went wrong while resetting customer password.') @@ -502,7 +502,7 @@ public function testResetPasswordActionSendEmail() $this->messageManager->expects( $this->once() )->method( - 'addSuccess' + 'addSuccessMessage' )->with( $this->equalTo('The customer will receive an email with a link to reset password.') ); diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php index 57f384d32d980..9724ac13dde8c 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php @@ -338,10 +338,12 @@ public function testExecuteWithExistentCustomer() $this->requestMock->expects($this->atLeastOnce()) ->method('getPostValue') - ->willReturnMap([ - [null, null, $postValue], - [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], - ]); + ->willReturnMap( + [ + [null, null, $postValue], + [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], + ] + ); $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( @@ -475,7 +477,7 @@ public function testExecuteWithExistentCustomer() ->with(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('You saved the customer.')) ->willReturnSelf(); @@ -542,10 +544,12 @@ public function testExecuteWithNewCustomer() $this->requestMock->expects($this->any()) ->method('getPostValue') - ->willReturnMap([ - [null, null, $postValue], - [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], - ]); + ->willReturnMap( + [ + [null, null, $postValue], + [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], + ] + ); $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( @@ -662,7 +666,7 @@ public function testExecuteWithNewCustomer() ->with(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId); $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') + ->method('addSuccessMessage') ->with(__('You saved the customer.')) ->willReturnSelf(); @@ -723,10 +727,12 @@ public function testExecuteWithNewCustomerAndValidationException() $this->requestMock->expects($this->any()) ->method('getPostValue') - ->willReturnMap([ - [null, null, $postValue], - [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], - ]); + ->willReturnMap( + [ + [null, null, $postValue], + [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], + ] + ); $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( @@ -804,7 +810,7 @@ public function testExecuteWithNewCustomerAndValidationException() ->method('register'); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->messageManagerMock->expects($this->once()) ->method('addMessage') @@ -812,10 +818,12 @@ public function testExecuteWithNewCustomerAndValidationException() $this->sessionMock->expects($this->once()) ->method('setCustomerFormData') - ->with([ - 'customer' => $extractedData, - 'subscription' => $subscription, - ]); + ->with( + [ + 'customer' => $extractedData, + 'subscription' => $subscription, + ] + ); /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) @@ -870,10 +878,12 @@ public function testExecuteWithNewCustomerAndLocalizedException() $this->requestMock->expects($this->any()) ->method('getPostValue') - ->willReturnMap([ - [null, null, $postValue], - [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], - ]); + ->willReturnMap( + [ + [null, null, $postValue], + [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], + ] + ); $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( @@ -951,7 +961,7 @@ public function testExecuteWithNewCustomerAndLocalizedException() ->method('register'); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->messageManagerMock->expects($this->once()) ->method('addMessage') @@ -959,10 +969,12 @@ public function testExecuteWithNewCustomerAndLocalizedException() $this->sessionMock->expects($this->once()) ->method('setCustomerFormData') - ->with([ - 'customer' => $extractedData, - 'subscription' => $subscription, - ]); + ->with( + [ + 'customer' => $extractedData, + 'subscription' => $subscription, + ] + ); /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) @@ -1017,10 +1029,12 @@ public function testExecuteWithNewCustomerAndException() $this->requestMock->expects($this->any()) ->method('getPostValue') - ->willReturnMap([ - [null, null, $postValue], - [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], - ]); + ->willReturnMap( + [ + [null, null, $postValue], + [CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER, null, $postValue['customer']], + ] + ); $this->requestMock->expects($this->atLeastOnce()) ->method('getPost') ->willReturnMap( @@ -1099,18 +1113,20 @@ public function testExecuteWithNewCustomerAndException() ->method('register'); $this->messageManagerMock->expects($this->never()) - ->method('addSuccess'); + ->method('addSuccessMessage'); $this->messageManagerMock->expects($this->once()) - ->method('addException') + ->method('addExceptionMessage') ->with($exception, __('Something went wrong while saving the customer.')); $this->sessionMock->expects($this->once()) ->method('setCustomerFormData') - ->with([ - 'customer' => $extractedData, - 'subscription' => $subscription, - ]); + ->with( + [ + 'customer' => $extractedData, + 'subscription' => $subscription, + ] + ); /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php index c92d4ed7812ba..55b4092af7141 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Locks/UnlockTest.php @@ -118,7 +118,7 @@ public function testExecute() ->with($this->equalTo('customer_id')) ->will($this->returnValue($customerId)); $this->authenticationMock->expects($this->once())->method('unlock')->with($customerId); - $this->messageManagerMock->expects($this->once())->method('addSuccess'); + $this->messageManagerMock->expects($this->once())->method('addSuccessMessage'); $this->redirectMock->expects($this->once()) ->method('setPath') ->with($this->equalTo('customer/index/edit')) @@ -141,7 +141,7 @@ public function testExecuteWithException() ->method('unlock') ->with($customerId) ->willThrowException(new \Exception($phrase)); - $this->messageManagerMock->expects($this->once())->method('addError'); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage'); $this->controller->execute(); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php index c2fc7b1b58756..1669063fb4f76 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php @@ -8,6 +8,7 @@ namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Backend\Model\Session; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\Message\MessageInterface; use Magento\Newsletter\Model\Subscriber; use Magento\Newsletter\Model\SubscriberFactory; @@ -75,7 +76,8 @@ public function testMassSubscriberAction() ], 'namespace' => 'customer_listing', ]; - $this->getRequest()->setParams($params); + $this->getRequest()->setParams($params) + ->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/massSubscribe'); @@ -109,7 +111,8 @@ public function testMassSubscriberActionNoSelection() 'namespace' => 'customer_listing' ]; - $this->getRequest()->setParams($params); + $this->getRequest()->setParams($params) + ->setMethod(HttpRequest::METHOD_POST); $this->dispatch('backend/customer/index/massSubscribe'); $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); From 424cd1fead90e091addd1acfa14061aaa1d66e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20K=C3=B6cke?= <jens@koecke.net> Date: Fri, 30 Aug 2019 08:33:43 +0200 Subject: [PATCH 540/841] magento/magento2#24362 Remove page layout definition from modules Every page layout definition in a modules layout file overwrites the page definition of a theme. --- .../view/frontend/layout/catalog_product_view.xml | 2 +- .../Magento/Msrp/view/frontend/layout/catalog_product_view.xml | 2 +- .../ProductVideo/view/frontend/layout/catalog_product_view.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/InstantPurchase/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/InstantPurchase/view/frontend/layout/catalog_product_view.xml index 6f8682197f615..5699fe6e00c7f 100644 --- a/app/code/Magento/InstantPurchase/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/InstantPurchase/view/frontend/layout/catalog_product_view.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="product.info.addtocart"> <block name="product.info.addtocart.instantPurchase" class="Magento\InstantPurchase\Block\Button" template="Magento_InstantPurchase::button.phtml" before="-"> diff --git a/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml index 78ec742cc09c6..18a5cb3e09359 100644 --- a/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Msrp/view/frontend/layout/catalog_product_view.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <!--<update handle="MAP_price_msrp_item"/>--> <update handle="msrp_popup"/> <body> diff --git a/app/code/Magento/ProductVideo/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/ProductVideo/view/frontend/layout/catalog_product_view.xml index 0b783e29ca0d3..38f7bf619f8fc 100644 --- a/app/code/Magento/ProductVideo/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/ProductVideo/view/frontend/layout/catalog_product_view.xml @@ -5,7 +5,7 @@ * See COPYING.txt for license details. */ --> -<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="product.info.media"> <block class="Magento\ProductVideo\Block\Product\View\Gallery" name="product.info.media.video" after="product.info.media.image" template="Magento_ProductVideo::product/view/gallery.phtml"/> From fc19cbdb8dcde1a54d6dbfcfb90b3eef35a35878 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Fri, 30 Aug 2019 12:07:04 +0300 Subject: [PATCH 541/841] MC-19438: Bundle Product Cart Pricing issue when adding tier price to bundle product and item is added twice to the cart --- .../BundleProductWithTierPriceInCartTest.xml | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml index 32a035e7eb1be..0d4840e01ddbd 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml @@ -15,21 +15,21 @@ <description value="Customer should be able to add one more bundle product to the cart and get the right price"/> <severity value="CRITICAL"/> <testCaseId value="MC-19727"/> - <group value="Bundle"/> + <group value="bundle"/> </annotations> <before> - <actionGroup ref="LoginAsAdmin" stepKey="login"/> <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> </before> <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> <argument name="product" value="BundleProduct"/> </actionGroup> - <actionGroup ref="AdminClearFiltersActionGroup" stepKey="ClearFiltersAfter"/> - <waitForPageLoad stepKey="waitForClearFilter"/> - <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearFiltersAfter"/> + <amOnPage url="{{AdminLogoutPage.url}}" stepKey="logout"/> </after> <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage"/> <waitForPageLoad stepKey="waitForBundleProductCreatePageToLoad"/> @@ -50,27 +50,27 @@ <argument name="optionTitle" value="Option2"/> <argument name="inputType" value="checkbox"/> </actionGroup> - <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <scrollToTopOfPage stepKey="scrollTopPageProduct"/> <actionGroup ref="ProductSetAdvancedPricing" stepKey="addTierPriceProduct1"> - <argument name="group" value="General"/> + <argument name="group" value="ALL GROUPS"/> <argument name="quantity" value="1"/> <argument name="price" value="Discount"/> <argument name="amount" value="50"/> </actionGroup> - <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="SignUpNewUser"> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="signUpNewUser"> <argument name="Customer" value="CustomerEntityOne"/> </actionGroup> - <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <amOnPage url="{{StorefrontProductPage.url(BundleProduct.urlKey)}}" stepKey="goToStorefront"/> <waitForPageLoad stepKey="waitForStorefront"/> - <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddtoCartButton"/> + <actionGroup ref="StorefrontSelectCustomizeAndAddToTheCartButtonActionGroup" stepKey="clickOnCustomizeAndAddToCartButton"/> <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCart"> <argument name="quantity" value="1"/> </actionGroup> <actionGroup ref="StorefrontEnterProductQuantityAndAddToTheCartActionGroup" stepKey="enterProductQuantityAndAddToTheCartAgain"> <argument name="quantity" value="1"/> </actionGroup> - <click stepKey="openMiniCart" selector="{{StorefrontMinicartSection.showCart}}"/> - <waitForPageLoad stepKey="waitForMiniCart"/> - <see stepKey="seeCartSubtotal" userInput="$246.00"/> + <actionGroup ref="AssertSubTotalOnStorefrontMiniCartActionGroup" stepKey="assertSubTotalOnStorefrontMiniCart"> + <argument name="subTotal" value="$246.00"/> + </actionGroup> </test> </tests> From 0788dd4a7404db879f97c4a643e58fb4c178d055 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 30 Aug 2019 13:16:11 +0300 Subject: [PATCH 542/841] MC-19585: CMS page redirects to homepage after changing store view --- app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php index 16e9c37ee4e52..873eed4466715 100644 --- a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php +++ b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php @@ -111,7 +111,7 @@ private function findCurrentRewrite(UrlRewrite $oldRewrite, StoreInterface $targ if (!$currentRewrite) { $currentRewrite = $this->urlFinder->findOneByData( [ - UrlRewrite::REQUEST_PATH => $oldRewrite->getTargetPath(), + UrlRewrite::REQUEST_PATH => $oldRewrite->getRequestPath(), UrlRewrite::STORE_ID => $targetStore->getId(), ] ); From 6aad82319c01a5235aba95f0034a1d8dcc4508c0 Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Fri, 30 Aug 2019 17:23:03 +0530 Subject: [PATCH 543/841] Add validation greater than zero --- app/code/Magento/Sitemap/etc/adminhtml/system.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sitemap/etc/adminhtml/system.xml b/app/code/Magento/Sitemap/etc/adminhtml/system.xml index 06dbaa79695be..320b9592f63b5 100644 --- a/app/code/Magento/Sitemap/etc/adminhtml/system.xml +++ b/app/code/Magento/Sitemap/etc/adminhtml/system.xml @@ -83,12 +83,12 @@ <label>Sitemap File Limits</label> <field id="max_lines" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum No of URLs Per File</label> - <validate>validate-number</validate> + <validate>validate-number validate-greater-than-zero</validate> </field> <field id="max_file_size" translate="label comment" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum File Size</label> <comment>File size in bytes.</comment> - <validate>validate-number</validate> + <validate>validate-number validate-greater-than-zero</validate> </field> </group> <group id="search_engines" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1"> From b536ccb820958435efe719c197764f50e57edc18 Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Fri, 30 Aug 2019 14:35:54 +0200 Subject: [PATCH 544/841] MC-18221: remove todo and exclusions of exception remove Required Refactoring see MC-19410 comments phpcs:disable Magento2.Exceptions.ThrowCatch --- app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php | 2 -- .../Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php | 2 -- .../Magento/Setup/Console/Command/ModuleConfigStatusCommand.php | 1 - 3 files changed, 5 deletions(-) diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php index 1d231ab6e5db1..91152bf51f026 100644 --- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php +++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php @@ -127,8 +127,6 @@ public function execute() $adminSession->destroy(); $response->setRedirectUrl($this->getUrl('*')); - // Required Refactoring see MC-19410 - // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Backup\Exception\CantLoadSnapshot $e) { $errorMsg = __('We can\'t find the backup file.'); } catch (\Magento\Framework\Backup\Exception\FtpConnectionFailed $e) { diff --git a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php index b3217f8bc22b7..fc396615e71e7 100644 --- a/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php +++ b/app/code/Magento/Theme/Controller/Adminhtml/System/Design/Theme/UploadJs.php @@ -52,8 +52,6 @@ public function execute() \Magento\Framework\View\Design\Theme\Customization\File\Js::TYPE ); $result = ['error' => false, 'files' => $customization->generateFileInfo($customJsFiles)]; - // Required Refactoring see MC-19410 - // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Magento\Framework\Exception\LocalizedException $e) { $result = ['error' => true, 'message' => $e->getMessage()]; } catch (\Exception $e) { diff --git a/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php b/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php index e191a63ff186c..70dbf14728bd7 100644 --- a/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php +++ b/setup/src/Magento/Setup/Console/Command/ModuleConfigStatusCommand.php @@ -97,7 +97,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln( '<info>The modules configuration is up to date.</info>' ); - // Required Refactoring see MC-19410 // phpcs:disable Magento2.Exceptions.ThrowCatch } catch (\Exception $e) { $output->writeln('<error>' . $e->getMessage() . '</error>'); From ac3cde143dbeb058d7078962e5c4d500c662453b Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Fri, 30 Aug 2019 08:30:49 -0500 Subject: [PATCH 545/841] Make const private --- app/code/Magento/Directory/Model/Data/ExchangeRate.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Directory/Model/Data/ExchangeRate.php b/app/code/Magento/Directory/Model/Data/ExchangeRate.php index 1efd0eb7f8cd0..500244304b7b8 100644 --- a/app/code/Magento/Directory/Model/Data/ExchangeRate.php +++ b/app/code/Magento/Directory/Model/Data/ExchangeRate.php @@ -20,7 +20,7 @@ class ExchangeRate extends \Magento\Framework\Api\AbstractExtensibleObject imple { const KEY_CURRENCY_TO = 'currency_to'; const KEY_RATE = 'rate'; - const KEY_EXCHANGE_RATES = 'exchange_rates'; + private const KEY_EXCHANGE_RATES = 'exchange_rates'; /** * @inheritDoc From cc486cbf2691feb1b735dc73ba84412fb9342ae4 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Fri, 30 Aug 2019 16:33:46 +0300 Subject: [PATCH 546/841] MC-19438: Bundle Product Cart Pricing issue when adding tier price to bundle product and item is added twice to the cart --- .../Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml index 0d4840e01ddbd..832fe9454f630 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml @@ -51,7 +51,7 @@ <argument name="inputType" value="checkbox"/> </actionGroup> <scrollToTopOfPage stepKey="scrollTopPageProduct"/> - <actionGroup ref="ProductSetAdvancedPricing" stepKey="addTierPriceProduct1"> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="addTierPriceProduct"> <argument name="group" value="ALL GROUPS"/> <argument name="quantity" value="1"/> <argument name="price" value="Discount"/> From 2a8291985b4b0d5a79ca8df0fe1c27d4040d934d Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Fri, 30 Aug 2019 11:55:57 -0500 Subject: [PATCH 547/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Allow exact filtering by sku --- .../Plugin/Search/Request/ConfigReader.php | 207 ++++++++++++------ .../FieldMapper/Product/AttributeAdapter.php | 13 ++ .../Product/FieldProvider/StaticField.php | 12 + 3 files changed, 166 insertions(+), 66 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php index 151be89177744..02f23e59f15d7 100644 --- a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php +++ b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php @@ -8,9 +8,12 @@ use Magento\Catalog\Api\Data\EavAttributeInterface; use Magento\CatalogSearch\Model\Search\RequestGenerator; use Magento\CatalogSearch\Model\Search\RequestGenerator\GeneratorResolver; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\Search\EngineResolverInterface; use Magento\Framework\Search\Request\FilterInterface; use Magento\Framework\Search\Request\QueryInterface; use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection; /** * Add search request configuration to config for give ability filter and search products during GraphQL request @@ -42,19 +45,27 @@ class ConfigReader */ private $productAttributeCollectionFactory; + /** + * @var EngineResolverInterface + */ + private $searchEngineResolver; + /** Bucket name suffix */ private const BUCKET_SUFFIX = '_bucket'; /** * @param GeneratorResolver $generatorResolver * @param CollectionFactory $productAttributeCollectionFactory + * @param EngineResolverInterface $searchEngineResolver */ public function __construct( GeneratorResolver $generatorResolver, - CollectionFactory $productAttributeCollectionFactory + CollectionFactory $productAttributeCollectionFactory, + EngineResolverInterface $searchEngineResolver ) { $this->generatorResolver = $generatorResolver; $this->productAttributeCollectionFactory = $productAttributeCollectionFactory; + $this->searchEngineResolver = $searchEngineResolver; } /** @@ -86,19 +97,19 @@ public function afterRead( /** * Retrieve searchable attributes * - * @return \Magento\Eav\Model\Entity\Attribute[] + * @return Attribute[] */ private function getSearchableAttributes(): array { $attributes = []; - /** @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection $productAttributes */ + /** @var Collection $productAttributes */ $productAttributes = $this->productAttributeCollectionFactory->create(); $productAttributes->addFieldToFilter( ['is_searchable', 'is_visible_in_advanced_search', 'is_filterable', 'is_filterable_in_search'], [1, 1, [1, 2], 1] ); - /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ + /** @var Attribute $attribute */ foreach ($productAttributes->getItems() as $attribute) { $attributes[$attribute->getAttributeCode()] = $attribute; } @@ -121,83 +132,35 @@ private function generateRequest() continue; } $queryName = $attribute->getAttributeCode() . '_query'; + $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX; $request['queries'][$this->requestNameWithAggregation]['queryReference'][] = [ 'clause' => 'must', 'ref' => $queryName, ]; + switch ($attribute->getBackendType()) { case 'static': case 'text': case 'varchar': if ($attribute->getFrontendInput() === 'multiselect') { - $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX; - $request['queries'][$queryName] = [ - 'name' => $queryName, - 'type' => QueryInterface::TYPE_FILTER, - 'filterReference' => [ - [ - 'ref' => $filterName, - ], - ], - ]; - $request['filters'][$filterName] = [ - 'type' => FilterInterface::TYPE_TERM, - 'name' => $filterName, - 'field' => $attribute->getAttributeCode(), - 'value' => '$' . $attribute->getAttributeCode() . '$', - ]; + $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName); + $request['filters'][$filterName] = $this->generateTermFilter($filterName, $attribute); + } elseif ($attribute->getAttributeCode() === 'sku') { + $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName); + $request['filters'][$filterName] = $this->generateSkuTermFilter($filterName, $attribute); } else { - $request['queries'][$queryName] = [ - 'name' => $queryName, - 'type' => 'matchQuery', - 'value' => '$' . $attribute->getAttributeCode() . '$', - 'match' => [ - [ - 'field' => $attribute->getAttributeCode(), - 'boost' => $attribute->getSearchWeight() ?: 1, - ], - ], - ]; + $request['queries'][$queryName] = $this->generateMatchQuery($queryName, $attribute); } break; case 'decimal': case 'datetime': case 'date': - $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX; - $request['queries'][$queryName] = [ - 'name' => $queryName, - 'type' => QueryInterface::TYPE_FILTER, - 'filterReference' => [ - [ - 'ref' => $filterName, - ], - ], - ]; - $request['filters'][$filterName] = [ - 'field' => $attribute->getAttributeCode(), - 'name' => $filterName, - 'type' => FilterInterface::TYPE_RANGE, - 'from' => '$' . $attribute->getAttributeCode() . '.from$', - 'to' => '$' . $attribute->getAttributeCode() . '.to$', - ]; + $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName); + $request['filters'][$filterName] = $this->generateRangeFilter($filterName, $attribute); break; default: - $filterName = $attribute->getAttributeCode() . RequestGenerator::FILTER_SUFFIX; - $request['queries'][$queryName] = [ - 'name' => $queryName, - 'type' => QueryInterface::TYPE_FILTER, - 'filterReference' => [ - [ - 'ref' => $filterName, - ], - ], - ]; - $request['filters'][$filterName] = [ - 'type' => FilterInterface::TYPE_TERM, - 'name' => $filterName, - 'field' => $attribute->getAttributeCode(), - 'value' => '$' . $attribute->getAttributeCode() . '$', - ]; + $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName); + $request['filters'][$filterName] = $this->generateTermFilter($filterName, $attribute); } $generator = $this->generatorResolver->getGeneratorForType($attribute->getBackendType()); @@ -215,11 +178,11 @@ private function generateRequest() /** * Add attribute with specified boost to "search" query used in full text search * - * @param \Magento\Eav\Model\Entity\Attribute $attribute + * @param Attribute $attribute * @param array $request * @return void */ - private function addSearchAttributeToFullTextSearch(\Magento\Eav\Model\Entity\Attribute $attribute, &$request): void + private function addSearchAttributeToFullTextSearch(Attribute $attribute, &$request): void { // Match search by custom price attribute isn't supported if ($attribute->getFrontendInput() !== 'price') { @@ -229,4 +192,116 @@ private function addSearchAttributeToFullTextSearch(\Magento\Eav\Model\Entity\At ]; } } + + /** + * Return array representation of range filter + * + * @param string $filterName + * @param Attribute $attribute + * @return array + */ + private function generateRangeFilter(string $filterName, Attribute $attribute) + { + return [ + 'field' => $attribute->getAttributeCode(), + 'name' => $filterName, + 'type' => FilterInterface::TYPE_RANGE, + 'from' => '$' . $attribute->getAttributeCode() . '.from$', + 'to' => '$' . $attribute->getAttributeCode() . '.to$', + ]; + } + + /** + * Return array representation of term filter + * + * @param string $filterName + * @param Attribute $attribute + * @return array + */ + private function generateTermFilter(string $filterName, Attribute $attribute) + { + return [ + 'type' => FilterInterface::TYPE_TERM, + 'name' => $filterName, + 'field' => $attribute->getAttributeCode(), + 'value' => '$' . $attribute->getAttributeCode() . '$', + ]; + } + + /** + * Generate term filter for sku field + * + * Sku needs to be treated specially to allow for exact match + * + * @param string $filterName + * @param Attribute $attribute + * @return array + */ + private function generateSkuTermFilter(string $filterName, Attribute $attribute) + { + $field = $this->isElasticSearch() ? 'sku.filter_sku' : 'sku'; + + return [ + 'type' => FilterInterface::TYPE_TERM, + 'name' => $filterName, + 'field' => $field, + 'value' => '$' . $attribute->getAttributeCode() . '$', + ]; + } + + /** + * Return array representation of query based on filter + * + * @param string $queryName + * @param string $filterName + * @return array + */ + private function generateFilterQuery(string $queryName, string $filterName) + { + return [ + 'name' => $queryName, + 'type' => QueryInterface::TYPE_FILTER, + 'filterReference' => [ + [ + 'ref' => $filterName, + ], + ], + ]; + } + + /** + * Return array representation of match query + * + * @param string $queryName + * @param Attribute $attribute + * @return array + */ + private function generateMatchQuery(string $queryName, Attribute $attribute) + { + return [ + 'name' => $queryName, + 'type' => 'matchQuery', + 'value' => '$' . $attribute->getAttributeCode() . '$', + 'match' => [ + [ + 'field' => $attribute->getAttributeCode(), + 'boost' => $attribute->getSearchWeight() ?: 1, + ], + ], + ]; + } + + /** + * Check if the current search engine is elasticsearch + * + * @return bool + */ + private function isElasticSearch() + { + $searchEngine = $this->searchEngineResolver->getCurrentSearchEngine(); + if (strpos($searchEngine, 'elasticsearch') === 0) { + return true; + } + return false; + } } diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php index 165f7e78eb65f..c3952b494f2e9 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php @@ -176,6 +176,19 @@ public function getFrontendInput() return $this->getAttribute()->getFrontendInput(); } + /** + * Check if product should always be filterable + * + * @return bool + */ + public function isAlwaysFilterable(): bool + { + // List of attributes which are required to be filterable + $alwaysFilterableAttributes = ['sku']; + + return in_array($this->getAttributeCode(), $alwaysFilterableAttributes, true); + } + /** * Get product attribute instance. * diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php index 6876b23bbb156..466bf5d2d09eb 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php @@ -130,6 +130,18 @@ public function getFields(array $context = []): array ]; } + if ($attributeAdapter->isAlwaysFilterable()) { + $filterFieldName = 'filter_' . $this->fieldNameResolver->getFieldName( + $attributeAdapter, + ['type' => FieldMapperInterface::TYPE_FILTER] + ); + $allAttributes[$fieldName]['fields'][$filterFieldName] = [ + 'type' => $this->fieldTypeConverter->convert( + FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD + ) + ]; + } + if ($attributeAdapter->isComplexType()) { $childFieldName = $this->fieldNameResolver->getFieldName( $attributeAdapter, From 2487db1b537c9d14f534d8628acf60581f5cbb1b Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Fri, 30 Aug 2019 12:15:50 -0500 Subject: [PATCH 548/841] MC-19689: Simple product disappearing in the configurable grid after qty set to 0 --- .../Plugin/Frontend/UsedProductsCache.php | 185 ++++++++++++++++++ .../Model/Product/Type/Configurable.php | 36 ++-- .../Magento/ConfigurableProduct/etc/di.xml | 8 + .../ConfigurableProduct/etc/frontend/di.xml | 3 + 4 files changed, 213 insertions(+), 19 deletions(-) create mode 100644 app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php diff --git a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php new file mode 100644 index 0000000000000..69c0a5f6000f0 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php @@ -0,0 +1,185 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Plugin\Frontend; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\Data\ProductInterfaceFactory; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\Product; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Customer\Model\Session; +use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Serialize\SerializerInterface; + +/** + * Cache of used products for configurable product + */ +class UsedProductsCache +{ + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @var FrontendInterface + */ + private $cache; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var ProductInterfaceFactory + */ + private $productFactory; + + /** + * @var Session + */ + private $customerSession; + + /** + * @param MetadataPool $metadataPool + * @param FrontendInterface $cache + * @param SerializerInterface $serializer + * @param ProductInterfaceFactory $productFactory + * @param Session $customerSession + */ + public function __construct( + MetadataPool $metadataPool, + FrontendInterface $cache, + SerializerInterface $serializer, + ProductInterfaceFactory $productFactory, + Session $customerSession + ) { + $this->metadataPool = $metadataPool; + $this->cache = $cache; + $this->serializer = $serializer; + $this->productFactory = $productFactory; + $this->customerSession = $customerSession; + } + + /** + * Retrieve used products for configurable product + * + * @param Configurable $subject + * @param callable $proceed + * @param Product $product + * @param array|null $requiredAttributeIds + * @return ProductInterface[] + */ + public function aroundGetUsedProducts( + Configurable $subject, + callable $proceed, + $product, + $requiredAttributeIds = null + ) { + $cacheKey = $this->getCacheKey($product, $requiredAttributeIds); + $usedProducts = $this->readUsedProductsCacheData($cacheKey); + if ($usedProducts === null) { + $usedProducts = $proceed($product, $requiredAttributeIds); + $this->saveUsedProductsCacheData($product, $usedProducts, $cacheKey); + } + + return $usedProducts; + } + + /** + * Generate cache key for product + * + * @param Product $product + * @param array|null $requiredAttributeIds + * @return string + */ + private function getCacheKey($product, $requiredAttributeIds = null): string + { + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $keyParts = [ + 'getUsedProducts', + $product->getData($metadata->getLinkField()), + $product->getStoreId(), + $this->customerSession->getCustomerGroupId(), + ]; + if ($requiredAttributeIds !== null) { + sort($requiredAttributeIds); + $keyParts[] = implode('', $requiredAttributeIds); + } + $cacheKey = sha1(implode('_', $keyParts)); + + return $cacheKey; + } + + /** + * Read used products data from cache + * + * Looking for cache record stored under provided $cacheKey + * In case data exists turns it into array of products + * + * @param string $cacheKey + * @return ProductInterface[]|null + */ + private function readUsedProductsCacheData(string $cacheKey): ?array + { + $data = $this->cache->load($cacheKey); + if (!$data) { + return null; + } + + $items = $this->serializer->unserialize($data); + if (!$items) { + return null; + } + + $usedProducts = []; + foreach ($items as $item) { + /** @var Product $productItem */ + $productItem = $this->productFactory->create(); + $productItem->setData($item); + $usedProducts[] = $productItem; + } + + return $usedProducts; + } + + /** + * Save $subProducts to cache record identified with provided $cacheKey + * + * Cached data will be tagged with combined list of product tags and data specific tags i.e. 'price' etc. + * + * @param Product $product + * @param ProductInterface[] $subProducts + * @param string $cacheKey + * @return bool + */ + private function saveUsedProductsCacheData(Product $product, array $subProducts, string $cacheKey): bool + { + $metadata = $this->metadataPool->getMetadata(ProductInterface::class); + $data = $this->serializer->serialize(array_map( + function ($item) { + return $item->getData(); + }, + $subProducts + )); + $tags = array_merge( + $product->getIdentities(), + [ + Category::CACHE_TAG, + Product::CACHE_TAG, + 'price', + Configurable::TYPE_CODE . '_' . $product->getData($metadata->getLinkField()) + ] + ); + $result = $this->cache->save($data, $cacheKey, $tags); + + return (bool) $result; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php index 77450748f7eba..c60953e33e9eb 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php @@ -1233,28 +1233,22 @@ public function isPossibleBuyFromList($product) * Returns array of sub-products for specified configurable product * * $requiredAttributeIds - one dimensional array, if provided - * * Result array contains all children for specified configurable product * - * @param \Magento\Catalog\Model\Product $product - * @param array $requiredAttributeIds + * @param \Magento\Catalog\Model\Product $product + * @param array $requiredAttributeIds * @return ProductInterface[] + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getUsedProducts($product, $requiredAttributeIds = null) { - $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); - $keyParts = [ - __METHOD__, - $product->getData($metadata->getLinkField()), - $product->getStoreId(), - $this->getCustomerSession()->getCustomerGroupId() - ]; - if ($requiredAttributeIds !== null) { - sort($requiredAttributeIds); - $keyParts[] = implode('', $requiredAttributeIds); + if (!$product->hasData($this->_usedProducts)) { + $collection = $this->getConfiguredUsedProductCollection($product, false); + $usedProducts = array_values($collection->getItems()); + $product->setData($this->_usedProducts, $usedProducts); } - $cacheKey = $this->getUsedProductsCacheKey($keyParts); - return $this->loadUsedProducts($product, $cacheKey); + + return $product->getData($this->_usedProducts); } /** @@ -1304,11 +1298,15 @@ private function loadUsedProducts(\Magento\Catalog\Model\Product $product, $cach { $dataFieldName = $salableOnly ? $this->usedSalableProducts : $this->_usedProducts; if (!$product->hasData($dataFieldName)) { - $collection = $this->getConfiguredUsedProductCollection($product, false); - if ($salableOnly) { - $collection = $this->salableProcessor->process($collection); + $usedProducts = $this->readUsedProductsCacheData($cacheKey); + if ($usedProducts === null) { + $collection = $this->getConfiguredUsedProductCollection($product, false); + if ($salableOnly) { + $collection = $this->salableProcessor->process($collection); + } + $usedProducts = array_values($collection->getItems()); + $this->saveUsedProductsCacheData($product, $usedProducts, $cacheKey); } - $usedProducts = array_values($collection->getItems()); $product->setData($dataFieldName, $usedProducts); } diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index b8f7ed67a9868..c8a278df92dc6 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -256,4 +256,12 @@ </argument> </arguments> </type> + <type name="Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache"> + <arguments> + <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Collection</argument> + </arguments> + <arguments> + <argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\Json</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml index df96829b354c8..b2d50f54f5334 100644 --- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml @@ -13,4 +13,7 @@ <type name="Magento\Catalog\Model\Product"> <plugin name="product_identities_extender" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\ProductIdentitiesExtender" /> </type> + <type name="Magento\ConfigurableProduct\Model\Product\Type\Configurable"> + <plugin name="used_products_cache" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\UsedProductsCache" /> + </type> </config> From 4b21bbf217d6da1b3a22ee9b6436733505c676fa Mon Sep 17 00:00:00 2001 From: Falk Ulbricht <f.ulbricht@techdivision.com> Date: Fri, 30 Aug 2019 20:30:11 +0200 Subject: [PATCH 549/841] MC-18221: Update composer lock --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 7f50f20b13321..50f0843d9e49a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "390ca0a394a73d3a70dd5a08f7a18674", + "content-hash": "d03365abc46d68e3654342a174871fb2", "packages": [ { "name": "braintree/braintree_php", From 781e7bba3d7cd4a90d6b15b6a767f48c1154f0d5 Mon Sep 17 00:00:00 2001 From: Sergey Solo <ssolomakhin@magecom.us> Date: Fri, 30 Aug 2019 23:34:58 +0300 Subject: [PATCH 550/841] github-issue; #2228; Fixes to Simplexml Element class to satisfy static tests --- .../Magento/Framework/Simplexml/Element.php | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/internal/Magento/Framework/Simplexml/Element.php b/lib/internal/Magento/Framework/Simplexml/Element.php index 1c039514d836c..6dd00ff5d7561 100644 --- a/lib/internal/Magento/Framework/Simplexml/Element.php +++ b/lib/internal/Magento/Framework/Simplexml/Element.php @@ -24,18 +24,6 @@ class Element extends \SimpleXMLElement */ protected $_parent = null; - /** - * For future use - * - * @param \Magento\Framework\Simplexml\Element $element - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function setParent($element) - { - //$this->_parent = $element; - } - /** * Returns parent node for the element * @@ -179,7 +167,8 @@ public function asArray() } /** - * asArray() analog, but without attributes + * The asArray() analog, but without attributes + * * @return array|string */ public function asCanonicalArray() @@ -469,8 +458,8 @@ public function setNode($path, $value, $overwrite = true) /** * Unset self from the XML-node tree - * * Note: trying to refer this object as a variable after "unsetting" like this will result in E_WARNING + * * @return void */ public function unsetSelf() From c9c6b457c5f9e02af4362789b87e64495987e185 Mon Sep 17 00:00:00 2001 From: Sankalp Shekhar <shekhar.sankalp@gmail.com> Date: Fri, 9 Aug 2019 08:48:25 -0500 Subject: [PATCH 551/841] Move WYSIWYG adapter to UI module --- .../Magento/Theme/view/base/layout/default.xml | 14 -------------- app/code/Magento/Ui/view/base/layout/default.xml | 1 + 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 app/code/Magento/Theme/view/base/layout/default.xml diff --git a/app/code/Magento/Theme/view/base/layout/default.xml b/app/code/Magento/Theme/view/base/layout/default.xml deleted file mode 100644 index b950b8efa963e..0000000000000 --- a/app/code/Magento/Theme/view/base/layout/default.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> - <body> - <referenceContainer name="after.body.start"> - <block class="Magento\Ui\Block\Wysiwyg\ActiveEditor" name="theme.active.editor" template="Magento_Ui::wysiwyg/active_editor.phtml" /> - </referenceContainer> - </body> -</page> diff --git a/app/code/Magento/Ui/view/base/layout/default.xml b/app/code/Magento/Ui/view/base/layout/default.xml index 979e6f74880a7..6c8f240660424 100644 --- a/app/code/Magento/Ui/view/base/layout/default.xml +++ b/app/code/Magento/Ui/view/base/layout/default.xml @@ -9,6 +9,7 @@ <body> <referenceContainer name="after.body.start"> <block class="Magento\Ui\Block\Logger" name="logger" template="Magento_Ui::logger.phtml"/> + <block class="Magento\Ui\Block\Wysiwyg\ActiveEditor" name="theme.active.editor" template="Magento_Ui::wysiwyg/active_editor.phtml" /> </referenceContainer> </body> </page> From 3051ff6096a6b1eb58e67ac7ce00fce333730bbc Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Fri, 30 Aug 2019 15:49:20 -0500 Subject: [PATCH 552/841] MC-19689: Simple product disappearing in the configurable grid after qty set to 0 --- .../Plugin/Frontend/UsedProductsCache.php | 19 +++-- .../Model/Product/Type/ConfigurableTest.php | 77 ++----------------- 2 files changed, 20 insertions(+), 76 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php index 69c0a5f6000f0..19a1b8d3ca17f 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php +++ b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/UsedProductsCache.php @@ -19,6 +19,8 @@ /** * Cache of used products for configurable product + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class UsedProductsCache { @@ -76,6 +78,7 @@ public function __construct( * @param Product $product * @param array|null $requiredAttributeIds * @return ProductInterface[] + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundGetUsedProducts( Configurable $subject, @@ -163,19 +166,21 @@ private function readUsedProductsCacheData(string $cacheKey): ?array private function saveUsedProductsCacheData(Product $product, array $subProducts, string $cacheKey): bool { $metadata = $this->metadataPool->getMetadata(ProductInterface::class); - $data = $this->serializer->serialize(array_map( - function ($item) { - return $item->getData(); - }, - $subProducts - )); + $data = $this->serializer->serialize( + array_map( + function ($item) { + return $item->getData(); + }, + $subProducts + ) + ); $tags = array_merge( $product->getIdentities(), [ Category::CACHE_TAG, Product::CACHE_TAG, 'price', - Configurable::TYPE_CODE . '_' . $product->getData($metadata->getLinkField()) + Configurable::TYPE_CODE . '_' . $product->getData($metadata->getLinkField()), ] ); $result = $this->cache->save($data, $cacheKey, $tags); diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php index c351d12fa813d..bf8357b5181d7 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php @@ -344,25 +344,13 @@ public function testCanUseAttribute() public function testGetUsedProducts() { - $productCollectionItemData = ['array']; + $productCollectionItem = $this->createMock(\Magento\Catalog\Model\Product::class); + $attributeCollection = $this->createMock(Collection::class); + $product = $this->createMock(\Magento\Catalog\Model\Product::class); + $productCollection = $this->createMock(ProductCollection::class); - $productCollectionItem = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - $attributeCollection = $this->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - $productCollection = $this->getMockBuilder(ProductCollection::class) - ->disableOriginalConstructor() - ->getMock(); - - $productCollectionItem->expects($this->once())->method('getData')->willReturn($productCollectionItemData); $attributeCollection->expects($this->any())->method('setProductFilter')->willReturnSelf(); $product->expects($this->atLeastOnce())->method('getStoreId')->willReturn(5); - $product->expects($this->once())->method('getIdentities')->willReturn(['123']); $product->expects($this->exactly(2)) ->method('hasData') @@ -388,59 +376,10 @@ public function testGetUsedProducts() $productCollection->expects($this->once())->method('setStoreId')->with(5)->willReturn([]); $productCollection->expects($this->once())->method('getItems')->willReturn([$productCollectionItem]); - $this->serializer->expects($this->once()) - ->method('serialize') - ->with([$productCollectionItemData]) - ->willReturn('result'); - $this->productCollectionFactory->expects($this->any())->method('create')->willReturn($productCollection); $this->model->getUsedProducts($product); } - public function testGetUsedProductsWithDataInCache() - { - $product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - $childProduct = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->disableOriginalConstructor() - ->getMock(); - - $dataKey = '_cache_instance_products'; - $usedProductsData = [['first']]; - $usedProducts = [$childProduct]; - - $product->expects($this->once()) - ->method('hasData') - ->with($dataKey) - ->willReturn(false); - $product->expects($this->once()) - ->method('setData') - ->with($dataKey, $usedProducts); - $product->expects($this->any()) - ->method('getData') - ->willReturnOnConsecutiveCalls(1, $usedProducts); - - $childProduct->expects($this->once()) - ->method('setData') - ->with($usedProductsData[0]); - - $this->productFactory->expects($this->once()) - ->method('create') - ->willReturn($childProduct); - - $this->cache->expects($this->once()) - ->method('load') - ->willReturn($usedProductsData); - - $this->serializer->expects($this->once()) - ->method('unserialize') - ->with($usedProductsData) - ->willReturn($usedProductsData); - - $this->assertEquals($usedProducts, $this->model->getUsedProducts($product)); - } - /** * @param int $productStore * @@ -878,12 +817,12 @@ public function testSetImageFromChildProduct() ->method('getLinkField') ->willReturn('link'); $productMock->expects($this->any())->method('hasData') - ->withConsecutive(['store_id'], ['_cache_instance_products']) - ->willReturnOnConsecutiveCalls(true, true); + ->withConsecutive(['_cache_instance_products']) + ->willReturnOnConsecutiveCalls(true); $productMock->expects($this->any())->method('getData') - ->withConsecutive(['image'], ['image'], ['link'], ['store_id'], ['_cache_instance_products']) - ->willReturnOnConsecutiveCalls('no_selection', 'no_selection', 1, 1, [$childProductMock]); + ->withConsecutive(['image'], ['image'], ['_cache_instance_products']) + ->willReturnOnConsecutiveCalls('no_selection', 'no_selection', [$childProductMock]); $childProductMock->expects($this->any())->method('getData')->with('image')->willReturn('image_data'); $productMock->expects($this->once())->method('setImage')->with('image_data')->willReturnSelf(); From ada83e4d4466da9f19be4df41b59c31773105e89 Mon Sep 17 00:00:00 2001 From: Sergey Solo <ssolomakhin@magecom.us> Date: Fri, 30 Aug 2019 23:59:31 +0300 Subject: [PATCH 553/841] github-issue; #2228; Fixes to Simplexml Element class to satisfy static tests --- lib/internal/Magento/Framework/Simplexml/Element.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/Magento/Framework/Simplexml/Element.php b/lib/internal/Magento/Framework/Simplexml/Element.php index 6dd00ff5d7561..2af729180aa16 100644 --- a/lib/internal/Magento/Framework/Simplexml/Element.php +++ b/lib/internal/Magento/Framework/Simplexml/Element.php @@ -458,6 +458,7 @@ public function setNode($path, $value, $overwrite = true) /** * Unset self from the XML-node tree + * * Note: trying to refer this object as a variable after "unsetting" like this will result in E_WARNING * * @return void From ef68feda5d4b289dfb7e3355e23323c5b3101438 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Fri, 30 Aug 2019 15:59:58 -0500 Subject: [PATCH 554/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Always apply relevance sort order --- .../Product/SearchCriteriaBuilder.php | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index 1e646b3c0b74b..af6ed85196cf8 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -96,11 +96,9 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte if (!empty($args['search'])) { $this->addFilter($searchCriteria, 'search_term', $args['search']); - if (!$searchCriteria->getSortOrders()) { - $this->addDefaultSortOrder($searchCriteria); - } } + $this->addDefaultSortOrder($searchCriteria); $this->addVisibilityFilter($searchCriteria, !empty($args['search']), !empty($args['filter'])); $searchCriteria->setCurrentPage($args['currentPage']); @@ -166,17 +164,27 @@ private function addFilter(SearchCriteriaInterface $searchCriteria, string $fiel } /** - * Sort by _score DESC if no sort order is set + * Sort by relevance DESC by default * * @param SearchCriteriaInterface $searchCriteria */ private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria): void { - $sortOrder = $this->sortOrderBuilder - ->setField('_score') + $sortOrders = $searchCriteria->getSortOrders() ?? []; + foreach ($sortOrders as $sortOrder) { + // Relevance order is already specified + if ($sortOrder->getField() === 'relevance') { + return; + } + } + $defaultSortOrder = $this->sortOrderBuilder + ->setField('relevance') ->setDirection(SortOrder::SORT_DESC) ->create(); - $searchCriteria->setSortOrders([$sortOrder]); + + $sortOrders[] = $defaultSortOrder; + + $searchCriteria->setSortOrders($sortOrders); } /** From 689a0b9719aa286e57858020077e6bb270ef42d9 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Fri, 30 Aug 2019 16:21:47 -0500 Subject: [PATCH 555/841] MC-19689: Simple product disappearing in the configurable grid after qty set to 0 --- .../Test/Unit/Model/Product/Type/ConfigurableTest.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php index bf8357b5181d7..165e479d99348 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/ConfigurableTest.php @@ -266,10 +266,12 @@ public function testSave() ->with('_cache_instance_used_product_attribute_ids') ->willReturn(true); $extensionAttributes = $this->getMockBuilder(ProductExtensionInterface::class) - ->setMethods([ - 'getConfigurableProductOptions', - 'getConfigurableProductLinks' - ]) + ->setMethods( + [ + 'getConfigurableProductOptions', + 'getConfigurableProductLinks' + ] + ) ->getMockForAbstractClass(); $this->entityMetadata->expects($this->any()) ->method('getLinkField') From 5eaef76ba5d8d777d28e79874b0db6af7e54d9a7 Mon Sep 17 00:00:00 2001 From: Sergey Solo <ssolomakhin@magecom.us> Date: Sat, 31 Aug 2019 00:39:54 +0300 Subject: [PATCH 556/841] github-issue; #2228; Fixes to Simplexml Element class to satisfy static tests --- .../Magento/Framework/Simplexml/Element.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/internal/Magento/Framework/Simplexml/Element.php b/lib/internal/Magento/Framework/Simplexml/Element.php index 2af729180aa16..27e59b5442763 100644 --- a/lib/internal/Magento/Framework/Simplexml/Element.php +++ b/lib/internal/Magento/Framework/Simplexml/Element.php @@ -24,6 +24,19 @@ class Element extends \SimpleXMLElement */ protected $_parent = null; + /** + * For future use + * + * @param \Magento\Framework\Simplexml\Element $element + * @return void + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * phpcs:disable Magento2.CodeAnalysis.EmptyBlock + */ + public function setParent($element) + { + } + // phpcs:enable + /** * Returns parent node for the element * From 48ae1fcf80d4b39df2e0699fcc3442f35bf3b299 Mon Sep 17 00:00:00 2001 From: Gustavo Dauer <gustavo.dauer@hotmail.com> Date: Fri, 30 Aug 2019 20:39:15 -0300 Subject: [PATCH 557/841] bugfix/MAGE24366 --- app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php index ac4a93e6066a4..77adbb4a2b67c 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php @@ -115,6 +115,8 @@ public function execute() */ private function updateItemQuantity(Item $item, float $qty) { + $item->clearMessage(); + if ($qty > 0) { $item->setQty($qty); From bc832bb065811ccb0dc58051bc4dbffe02d9a77e Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Sat, 31 Aug 2019 12:17:09 +0300 Subject: [PATCH 558/841] graphQl-810: Static fix --- .../AddConfigurableProductToCartTest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php index 8bce845f77591..a74ea5451d965 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php @@ -117,8 +117,6 @@ public function testAddNonExistentConfigurableProductParentToCart() /** * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable_sku.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * @expectedException \Exception - * @expectedExceptionMessage Could not add the product with SKU configurable to the shopping cart: Could not find specified product. */ public function testAddNonExistentConfigurableProductVariationToCart() { @@ -136,6 +134,11 @@ public function testAddNonExistentConfigurableProductVariationToCart() 2000 ); + $this->expectException(\Exception::class); + $this->expectExceptionMessage( + 'Could not add the product with SKU configurable to the shopping cart: Could not find specified product.' + ); + $this->graphQlMutation($query); } From 9d2c606eca7196970a066337fc13d14f14acc9c8 Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Sat, 31 Aug 2019 12:24:36 +0300 Subject: [PATCH 559/841] graphQl-825: Static fix --- ...ableProductWithCustomOptionsToCartTest.php | 23 +++++++++++++------ .../GetCustomOptionsValuesForQueryBySku.php | 3 +-- ...oduct_downloadable_with_custom_options.php | 6 +++-- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php index d0348baadddcc..fa7d1194c7f83 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddDownloadableProductWithCustomOptionsToCartTest.php @@ -39,7 +39,8 @@ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); $this->getMaskedQuoteIdByReservedOrderId = $this->objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); - $this->getCustomOptionsValuesForQueryBySku = $this->objectManager->get(GetCustomOptionsValuesForQueryBySku::class); + $this->getCustomOptionsValuesForQueryBySku = + $this->objectManager->get(GetCustomOptionsValuesForQueryBySku::class); } /** @@ -65,7 +66,8 @@ public function testAddDownloadableProductWithOptions() $response = $this->graphQlMutation($query); self::assertArrayHasKey('items', $response['addDownloadableProductsToCart']['cart']); self::assertCount($qty, $response['addDownloadableProductsToCart']['cart']); - $customizableOptionsOutput = $response['addDownloadableProductsToCart']['cart']['items'][0]['customizable_options']; + $customizableOptionsOutput = + $response['addDownloadableProductsToCart']['cart']['items'][0]['customizable_options']; $assignedOptionsCount = count($customOptionsValues); for ($counter = 0; $counter < $assignedOptionsCount; $counter++) { $expectedValues = $this->buildExpectedValuesArray($customOptionsValues[$counter]['value_string']); @@ -79,9 +81,6 @@ public function testAddDownloadableProductWithOptions() /** * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_custom_options.php * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php - * - * @expectedException \Exception - * @expectedExceptionMessage The product's required option(s) weren't entered. Make sure the options are entered and try again. */ public function testAddDownloadableProductWithMissedRequiredOptionsSet() { @@ -95,6 +94,11 @@ public function testAddDownloadableProductWithMissedRequiredOptionsSet() $query = $this->getQuery($maskedQuoteId, $qty, $sku, $customizableOptions, $linkId); + $this->expectException(\Exception::class); + $this->expectExceptionMessage( + 'The product\'s required option(s) weren\'t entered. Make sure the options are entered and try again.' + ); + $this->graphQlMutation($query); } @@ -148,8 +152,13 @@ private function buildExpectedValuesArray(string $assignedValue) : array * @param $linkId * @return string */ - private function getQuery(string $maskedQuoteId, int $qty, string $sku, string $customizableOptions, $linkId): string - { + private function getQuery( + string $maskedQuoteId, + int $qty, + string $sku, + string $customizableOptions, + $linkId + ): string { $query = <<<MUTATION mutation { addDownloadableProductsToCart( diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php index 58f193e6ca6e4..7514eb1c4e1d0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCustomOptionsValuesForQueryBySku.php @@ -10,8 +10,7 @@ use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; /** - * Generate an array with test values for customizable options - * based on the option type + * Generate an array with test values for customizable options based on the option type */ class GetCustomOptionsValuesForQueryBySku { diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php index bf7a2600470c5..b5528dd27ee7c 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/_files/product_downloadable_with_custom_options.php @@ -81,7 +81,8 @@ $customOptions = []; /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory */ -$customOptionFactory = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); +$customOptionFactory = Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); foreach ($options as $option) { /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterface $customOption */ @@ -94,5 +95,6 @@ $product->setOptions($customOptions); /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */ -$productRepositoryFactory = Bootstrap::getObjectManager()->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepositoryFactory = Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); $productRepositoryFactory->save($product); From f02e79155499978aebe17d9419dcd712346aa912 Mon Sep 17 00:00:00 2001 From: Denys Soloviov <solovyov.denis@gmail.com> Date: Sat, 31 Aug 2019 14:58:26 +0300 Subject: [PATCH 560/841] MAGETWO-24349: Added br as allowed tag --- .../Sales/view/adminhtml/templates/items/column/name.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml b/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml index 64600f31b47de..c9b2f7c8de254 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml @@ -29,7 +29,7 @@ <?php $_option = $block->getFormattedOption($_option['value']); ?> <?php $dots = 'dots' . uniqid(); ?> <?php $id = 'id' . uniqid(); ?> - <?= $block->escapeHtml($_option['value'], ['a']) ?><?php if (isset($_option['remainder']) && $_option['remainder']) : ?><span id="<?= /* @noEscape */ $dots; ?>"> ...</span><span id="<?= /* @noEscape */ $id; ?>"><?= $block->escapeHtml($_option['remainder'], ['a']) ?></span> + <?= $block->escapeHtml($_option['value'], ['a', 'br']) ?><?php if (isset($_option['remainder']) && $_option['remainder']) : ?><span id="<?= /* @noEscape */ $dots; ?>"> ...</span><span id="<?= /* @noEscape */ $id; ?>"><?= $block->escapeHtml($_option['remainder'], ['a']) ?></span> <script> require(['prototype'], function() { $('<?= /* @noEscape */ $id; ?>').hide(); From 66290e6fc0c3c9308c964f689ee590091a2ff988 Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Sat, 31 Aug 2019 15:01:15 +0300 Subject: [PATCH 561/841] graphQl-864: Prevent to return a shipping address until it be set on a cart --- .../Model/Resolver/ShippingAddresses.php | 4 ++++ .../Customer/GetSpecifiedShippingAddressTest.php | 16 ++++++++++++++++ .../Guest/GetSpecifiedShippingAddressTest.php | 15 +++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php index eb3b0966740eb..eebe2a66e92fd 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php @@ -44,6 +44,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $cart = $value['model']; $addressesData = []; + if (!$cart->getExtensionAttributes()->getShippingAssignments()) { + return $addressesData; + } + $shippingAddresses = $cart->getAllShippingAddresses(); if (count($shippingAddresses)) { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php index de3c384e65783..3b4e5edb917ca 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php @@ -77,6 +77,22 @@ public function testGetSpecifiedShippingAddress() self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); } + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + */ + public function testShippingAddressOnCreatedEmptyCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + + self::assertCount(0, $response['cart']['shipping_addresses']); + } + /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php index f71915bab650f..0809f9b136604 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php @@ -68,6 +68,21 @@ public function testGetSpecifiedShippingAddress() self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); } + /** + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + */ + public function testShippingAddressOnCreatedEmptyCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + $query = $this->getQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query); + self::assertArrayHasKey('cart', $response); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + + self::assertCount(0, $response['cart']['shipping_addresses']); + } + /** * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php From 3cebfd37293db2f8deac47cec078c7edc2290fd6 Mon Sep 17 00:00:00 2001 From: rani-webkul <rani.priya4392webkul.com> Date: Sat, 31 Aug 2019 19:22:57 +0530 Subject: [PATCH 562/841] fixed design issue on sales order view page frontend #24395 --- .../Magento/Sales/view/frontend/templates/order/items.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/view/frontend/templates/order/items.phtml b/app/code/Magento/Sales/view/frontend/templates/order/items.phtml index 98a1f1ebb7545..8762718c6d849 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/items.phtml @@ -29,12 +29,12 @@ </thead> <?php $items = $block->getItems(); ?> <?php $giftMessage = ''?> - <tbody> <?php foreach ($items as $item) : if ($item->getParentItem()) : continue; endif; ?> + <tbody> <?= $block->getItemHtml($item) ?> <?php if ($this->helper(\Magento\GiftMessage\Helper\Message::class)->isMessagesAllowed('order_item', $item) && $item->getGiftMessageId()) : ?> <?php $giftMessage = $this->helper(\Magento\GiftMessage\Helper\Message::class)->getGiftMessageForEntity($item); ?> @@ -65,8 +65,8 @@ </td> </tr> <?php endif ?> + </tbody> <?php endforeach; ?> - </tbody> <tfoot> <?php if ($block->isPagerDisplayed()) : ?> <tr> From 24c022ec67c70ea60dca7e8c9f0b2ed0eada27fd Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sun, 1 Sep 2019 11:28:54 +0700 Subject: [PATCH 563/841] Resolve "New Block" form still show "Store View" field in "Single Store Mode" issue24387 --- .../Cms/Ui/Component/Form/Field/StoreView.php | 62 +++++++++++++++++++ .../adminhtml/ui_component/cms_block_form.xml | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Cms/Ui/Component/Form/Field/StoreView.php diff --git a/app/code/Magento/Cms/Ui/Component/Form/Field/StoreView.php b/app/code/Magento/Cms/Ui/Component/Form/Field/StoreView.php new file mode 100644 index 0000000000000..3a7424bd35ee0 --- /dev/null +++ b/app/code/Magento/Cms/Ui/Component/Form/Field/StoreView.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cms\Ui\Component\Form\Field; + +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\UiComponentFactory; +use Magento\Framework\View\Element\UiComponentInterface; +use Magento\Store\Model\StoreManagerInterface as StoreManager; +use Magento\Ui\Component\Form\Field; + +/** + * Check to disable store view field + * + * Class \Magento\Cms\Ui\Component\Form\Field\StoreView + */ +class StoreView extends Field +{ + /** + * Store manager + * + * @var StoreManager + */ + private $storeManager; + + /** + * StoreView constructor. + * + * @param ContextInterface $context + * @param UiComponentFactory $uiComponentFactory + * @param StoreManager $storeManager + * @param array $components + * @param array $data + */ + public function __construct( + ContextInterface $context, + UiComponentFactory $uiComponentFactory, + StoreManager $storeManager, + array $components = [], + array $data = [] + ) { + parent::__construct($context, $uiComponentFactory, $components, $data); + $this->storeManager = $storeManager; + } + + /** + * Prepare component configuration + * + * @return void + */ + public function prepare() + { + parent::prepare(); + if ($this->storeManager->isSingleStoreMode()) { + $this->_data['config']['componentDisabled'] = true; + } + } +} diff --git a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml index adee89d011460..83e53f96e0653 100644 --- a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml +++ b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml @@ -111,7 +111,7 @@ <dataScope>identifier</dataScope> </settings> </field> - <field name="storeviews" formElement="multiselect"> + <field name="storeviews" formElement="multiselect" class="Magento\Cms\Ui\Component\Form\Field\StoreView"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="source" xsi:type="string">block</item> From 0f38f03418d4228e4e3c4898f9e95508ecef25ca Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 2 Sep 2019 08:59:25 +0700 Subject: [PATCH 564/841] Resolve Vat ID not being included in Shipping Address in Magento 2.3.2 issue24402 --- .../Customer/Model/Address/CustomerAddressDataFormatter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php b/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php index 9202d7492040c..dfda2bd324828 100644 --- a/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php +++ b/app/code/Magento/Customer/Model/Address/CustomerAddressDataFormatter.php @@ -81,6 +81,7 @@ public function prepareAddress(AddressInterface $customerAddress) 'inline' => $this->getCustomerAddressInline($customerAddress), 'custom_attributes' => [], 'extension_attributes' => $customerAddress->getExtensionAttributes(), + 'vat_id' => $customerAddress->getVatId() ]; if ($customerAddress->getCustomAttributes()) { From 8f9d81d78c90ea4ad35714b7004e6545d0c465cc Mon Sep 17 00:00:00 2001 From: Veronika Kurochkina <veronika_kurochkina@epam.com> Date: Mon, 2 Sep 2019 12:13:02 +0300 Subject: [PATCH 565/841] MAGETWO-44170: Not pass function test \Magento\Catalog\Test\TestCase\Product\ProductTypeSwitchingOnUpdateTest - Fix mftf --- .../Test/AdminProductTypeSwitchingOnEditingTest.xml | 4 ++-- .../Test/AdminProductTypeSwitchingOnEditingTest.xml | 12 ++++++------ .../Test/AdminProductTypeSwitchingOnEditingTest.xml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 42623f43d4e82..a11646cc46875 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -11,7 +11,7 @@ <test name="AdminVirtualProductTypeSwitchingToDownloadableProductTest"> <annotations> <features value="Catalog"/> - <stories value="Switch product type"/> + <stories value="Product type switching"/> <title value="Virtual product type switching on editing to Downloadable product"/> <description value="Virtual product type switching on editing to Downloadable product"/> <testCaseId value="MC-17954"/> @@ -62,7 +62,7 @@ <test name="AdminDownloadableProductTypeSwitchingToSimpleProductTest" extends="AdminVirtualProductTypeSwitchingToDownloadableProductTest"> <annotations> <features value="Catalog"/> - <stories value="Switch product type"/> + <stories value="Product type switching"/> <title value="Downloadable product type switching on editing to Simple product"/> <description value="Downloadable product type switching on editing to Simple product"/> <testCaseId value="MC-17955"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 8253dcf58f9fd..fa21d20eb4456 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -10,8 +10,8 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> <annotations> - <features value="Catalog"/> - <stories value="Switch product type"/> + <features value="ConfigurableProduct"/> + <stories value="Product type switching"/> <title value="Simple product type switching on editing to configurable product"/> <description value="Simple product type switching on editing to configurable product"/> <testCaseId value="MAGETWO-29633"/> @@ -75,8 +75,8 @@ </test> <test name="AdminConfigurableProductTypeSwitchingToVirtualProductTest" extends="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> <annotations> - <features value="Catalog"/> - <stories value="Switch product type"/> + <features value="ConfigurableProduct"/> + <stories value="Product type switching"/> <title value="Configurable product type switching on editing to virtual product"/> <description value="Configurable product type switching on editing to virtual product"/> <testCaseId value="MC-17952"/> @@ -114,8 +114,8 @@ </test> <test name="AdminVirtualProductTypeSwitchingToConfigurableProductTest"> <annotations> - <features value="Catalog"/> - <stories value="Switch product type"/> + <features value="ConfigurableProduct"/> + <stories value="Product type switching"/> <title value="Virtual product type switching on editing to configurable product"/> <description value="Virtual product type switching on editing to configurable product"/> <testCaseId value="MC-17953"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml index 80e86ab4d747c..8cb0d5fde9863 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest.xml @@ -10,8 +10,8 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminDownloadableProductTypeSwitchingToConfigurableProductTest" extends="AdminSimpleProductTypeSwitchingToConfigurableProductTest"> <annotations> - <features value="Catalog"/> - <stories value="Switch product type"/> + <features value="Downloadable"/> + <stories value="Product type switching"/> <title value="Downloadable product type switching on editing to configurable product"/> <description value="Downloadable product type switching on editing to configurable product"/> <testCaseId value="MC-17957"/> @@ -27,8 +27,8 @@ </test> <test name="AdminSimpleProductTypeSwitchingToDownloadableProductTest"> <annotations> - <features value="Catalog"/> - <stories value="Switch product type"/> + <features value="Downloadable"/> + <stories value="Product type switching"/> <title value="Simple product type switching on editing to downloadable product"/> <description value="Simple product type switching on editing to downloadable product"/> <testCaseId value="MC-17956"/> From 7e3ab4ba0fb53f824df4a709127c537f98612ad7 Mon Sep 17 00:00:00 2001 From: Tjitse <Tjitse@vendic.nl> Date: Mon, 2 Sep 2019 11:27:38 +0200 Subject: [PATCH 566/841] Remove excess line before method body --- .../View/Design/FileResolution/Fallback/Resolver/Simple.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/Magento/Framework/View/Design/FileResolution/Fallback/Resolver/Simple.php b/lib/internal/Magento/Framework/View/Design/FileResolution/Fallback/Resolver/Simple.php index fb2d8f4290881..d625ad1523554 100644 --- a/lib/internal/Magento/Framework/View/Design/FileResolution/Fallback/Resolver/Simple.php +++ b/lib/internal/Magento/Framework/View/Design/FileResolution/Fallback/Resolver/Simple.php @@ -54,7 +54,6 @@ public function __construct(ReadFactory $readFactory, RulePool $rulePool) */ public function resolve($type, $file, $area = null, ThemeInterface $theme = null, $locale = null, $module = null) { - $params = ['area' => $area, 'theme' => $theme, 'locale' => $locale]; foreach ($params as $key => $param) { if ($param === null) { From 7ff3c270e7ea24787d5a3804c8ef9f69815b2884 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Mon, 2 Sep 2019 14:23:54 +0300 Subject: [PATCH 567/841] magento/magento2#23764: Deprecated code removed. --- .../Customer/Test/Unit/Model/CustomerExtractorTest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php index ac8c60a23db80..6fd5c76da81c0 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerExtractorTest.php @@ -144,16 +144,6 @@ public function testExtract() $this->store->expects($this->once()) ->method('getId') ->willReturn(1); -// $this->customerGroupManagement->expects($this->once()) -// ->method('getDefaultGroup') -// ->with(1) -// ->willReturn($this->customerGroup); -// $this->customerGroup->expects($this->once()) -// ->method('getId') -// ->willReturn(1); -// $this->customerData->expects($this->once()) -// ->method('setGroupId') -// ->with(1); $this->store->expects($this->once()) ->method('getWebsiteId') ->willReturn(1); From 03287f002772d5f2bc216a3d1c91f57323c38c43 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 2 Sep 2019 15:04:48 +0300 Subject: [PATCH 568/841] MC-19585: CMS page redirects to homepage after changing store view --- .../Model/StoreSwitcher/RewriteUrlTest.php | 88 +++++++++++++++---- 1 file changed, 70 insertions(+), 18 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php index a8ff9e411785e..3e423d239665e 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php @@ -8,14 +8,20 @@ namespace Magento\UrlRewrite\Model\StoreSwitcher; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Cms\Model\Page; use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\App\Config\Value; use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Model\StoreSwitcher; use Magento\Framework\ObjectManagerInterface as ObjectManager; use Magento\TestFramework\Helper\Bootstrap; +/** + * Test store switching + */ class RewriteUrlTest extends \PHPUnit\Framework\TestCase { /** @@ -33,6 +39,11 @@ class RewriteUrlTest extends \PHPUnit\Framework\TestCase */ private $productRepository; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * Class dependencies initialization * @@ -43,9 +54,12 @@ protected function setUp() $this->objectManager = Bootstrap::getObjectManager(); $this->storeSwitcher = $this->objectManager->get(StoreSwitcher::class); $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class); + $this->storeManager = $this->objectManager->create(StoreManagerInterface::class); } /** + * Test switching stores with non-existent cms pages and then redirecting to the homepage + * * @magentoDataFixture Magento/UrlRewrite/_files/url_rewrite.php * @magentoDataFixture Magento/Catalog/_files/category_product.php * @return void @@ -54,15 +68,8 @@ protected function setUp() */ public function testSwitchToNonExistingPage(): void { - $fromStoreCode = 'default'; - /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ - $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); - $fromStore = $storeRepository->get($fromStoreCode); - - $toStoreCode = 'fixture_second_store'; - /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ - $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); - $toStore = $storeRepository->get($toStoreCode); + $fromStore = $this->getStoreByCode('default'); + $toStore = $this->getStoreByCode('fixture_second_store'); $this->setBaseUrl($toStore); @@ -75,6 +82,8 @@ public function testSwitchToNonExistingPage(): void } /** + * Testing store switching with existing cms pages + * * @magentoDataFixture Magento/UrlRewrite/_files/url_rewrite.php * @return void * @throws StoreSwitcher\CannotSwitchStoreException @@ -82,15 +91,8 @@ public function testSwitchToNonExistingPage(): void */ public function testSwitchToExistingPage(): void { - $fromStoreCode = 'default'; - /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ - $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); - $fromStore = $storeRepository->get($fromStoreCode); - - $toStoreCode = 'fixture_second_store'; - /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ - $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); - $toStore = $storeRepository->get($toStoreCode); + $fromStore = $this->getStoreByCode('default'); + $toStore = $this->getStoreByCode('fixture_second_store'); $redirectUrl = "http://localhost/index.php/page-c/"; $expectedUrl = "http://localhost/index.php/page-c-on-2nd-store"; @@ -98,6 +100,25 @@ public function testSwitchToExistingPage(): void $this->assertEquals($expectedUrl, $this->storeSwitcher->switch($fromStore, $toStore, $redirectUrl)); } + /** + * Testing store switching using cms pages with the same url_key but with different page_id + * + * @magentoDataFixture Magento/Cms/_files/pages.php + * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDbIsolation disabled + * @return void + */ + public function testSwitchCmsPageToAnotherStore(): void + { + $storeId = (int)$this->storeManager->getStore('fixture_second_store')->getId(); + $this->createCmsPage($storeId); + $fromStore = $this->getStoreByCode('default'); + $toStore = $this->getStoreByCode('fixture_second_store'); + $redirectUrl = "http://localhost/index.php/page100/"; + $expectedUrl = "http://localhost/index.php/page100/"; + $this->assertEquals($expectedUrl, $this->storeSwitcher->switch($fromStore, $toStore, $redirectUrl)); + } + /** * Set base url to store. * @@ -120,4 +141,35 @@ private function setBaseUrl(StoreInterface $targetStore): void $reinitibleConfig = $this->objectManager->create(ReinitableConfigInterface::class); $reinitibleConfig->reinit(); } + + /** + * Get store object by storeCode + * + * @param string $storeCode + * @return StoreInterface + */ + private function getStoreByCode(string $storeCode): StoreInterface + { + /** @var StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->create(StoreRepositoryInterface::class); + return $storeRepository->get($storeCode); + } + + /** + * Create cms page for store with store id from parameters + * + * @param int $storeId + * @return void + */ + private function createCmsPage(int $storeId): void + { + /** @var $page \Magento\Cms\Model\Page */ + $page = $this->objectManager->create(Page::class); + $page->setTitle('Test cms page') + ->setIdentifier('page100') + ->setStores([$storeId]) + ->setIsActive(1) + ->setPageLayout('1column') + ->save(); + } } From 23b0061e057e482b1b0347b763b35125a6ed0c37 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 2 Sep 2019 16:12:48 +0300 Subject: [PATCH 569/841] MC-19197: "Illegal state" error on PLP page --- .../LayeredNavigation/Block/Navigation.php | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/LayeredNavigation/Block/Navigation.php b/app/code/Magento/LayeredNavigation/Block/Navigation.php index 4173469da8e42..25c6edd1d61aa 100644 --- a/app/code/Magento/LayeredNavigation/Block/Navigation.php +++ b/app/code/Magento/LayeredNavigation/Block/Navigation.php @@ -3,22 +3,25 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - -/** - * Catalog layered navigation view block - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\LayeredNavigation\Block; use Magento\Framework\View\Element\Template; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Block\Product\ProductList\Toolbar; /** + * Catalog layered navigation view block + * * @api * @since 100.0.2 */ class Navigation extends \Magento\Framework\View\Element\Template { + /** + * Product listing toolbar block name + */ + private const PRODUCT_LISTING_TOOLBAR_BLOCK = 'product_list_toolbar'; + /** * Catalog layer * @@ -67,9 +70,20 @@ protected function _prepareLayout() $filter->apply($this->getRequest()); } $this->getLayer()->apply(); + return parent::_prepareLayout(); } + /** + * @inheritdoc + */ + protected function _beforeToHtml() + { + $this->configureToolbarBlock(); + + return parent::_beforeToHtml(); + } + /** * Get layer object * @@ -119,4 +133,20 @@ public function getClearUrl() { return $this->getChildBlock('state')->getClearUrl(); } + + /** + * Configures the Toolbar block + * + * @return void + */ + private function configureToolbarBlock(): void + { + /** @var Toolbar $toolbarBlock */ + $toolbarBlock = $this->getLayout()->getBlock(self::PRODUCT_LISTING_TOOLBAR_BLOCK); + if ($toolbarBlock) { + /** @var Collection $collection */ + $collection = $this->getLayer()->getProductCollection(); + $toolbarBlock->setCollection($collection); + } + } } From 64b64652d6c2b94021a5de9850e9f0a09873e0cb Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 2 Sep 2019 16:17:00 +0300 Subject: [PATCH 570/841] MC-19585: CMS page redirects to homepage after changing store view --- .../Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php index 3e423d239665e..800f7e53bd68b 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php @@ -20,6 +20,7 @@ use Magento\TestFramework\Helper\Bootstrap; /** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * Test store switching */ class RewriteUrlTest extends \PHPUnit\Framework\TestCase From 95cad1ae94b01bcef66ee7ff63a9d489d45ff911 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Mon, 2 Sep 2019 18:53:54 +0530 Subject: [PATCH 571/841] change shopping cart to minicart --- app/code/Magento/Checkout/etc/adminhtml/system.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Checkout/etc/adminhtml/system.xml b/app/code/Magento/Checkout/etc/adminhtml/system.xml index 11e3ba5f3ed9a..34e667331f7f7 100644 --- a/app/code/Magento/Checkout/etc/adminhtml/system.xml +++ b/app/code/Magento/Checkout/etc/adminhtml/system.xml @@ -54,9 +54,9 @@ </field> </group> <group id="sidebar" translate="label" sortOrder="4" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Shopping Cart Sidebar</label> + <label>Mini Cart</label> <field id="display" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> - <label>Display Shopping Cart Sidebar</label> + <label>Display Mini Cart</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> </field> <field id="count" translate="label" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> From e620c8e72a292e2f4b1555528950852af7fa5f76 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Mon, 2 Sep 2019 16:42:16 +0300 Subject: [PATCH 572/841] MC-19438: Bundle Product Cart Pricing issue when adding tier price to bundle product and item is added twice to the cart --- .../Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml index 832fe9454f630..46c6114637af6 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductWithTierPriceInCartTest.xml @@ -25,10 +25,14 @@ <after> <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <actionGroup ref="StorefrontSignOutActionGroup" stepKey="StorefrontSignOutActionGroup"/> <actionGroup stepKey="deleteBundle" ref="deleteProductUsingProductGrid"> <argument name="product" value="BundleProduct"/> </actionGroup> <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearFiltersAfter"/> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> + <argument name="customerEmail" value="CustomerEntityOne.email"/> + </actionGroup> <amOnPage url="{{AdminLogoutPage.url}}" stepKey="logout"/> </after> <amOnPage url="{{AdminProductCreatePage.url(BundleProduct.set, BundleProduct.type)}}" stepKey="goToBundleProductCreationPage"/> From 778b42b6b9ce7f083c7e7adf45eb588519b7e538 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Mon, 2 Sep 2019 17:12:52 +0300 Subject: [PATCH 573/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- app/code/Magento/CatalogInventory/etc/adminhtml/system.xml | 2 +- .../view/adminhtml/ui_component/product_form.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml index 08ed0a8f49470..546f838b9b428 100644 --- a/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml +++ b/app/code/Magento/CatalogInventory/etc/adminhtml/system.xml @@ -55,7 +55,7 @@ </field> <field id="max_sale_qty" translate="label" type="text" sortOrder="4" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Maximum Qty Allowed in Shopping Cart</label> - <validate>validate-number</validate> + <validate>validate-number validate-greater-than-zero</validate> </field> <field id="min_qty" translate="label" type="text" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Out-of-Stock Threshold</label> diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml index fc0690157fb37..b813aa5d356cb 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml +++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml @@ -304,6 +304,7 @@ <settings> <scopeLabel>[GLOBAL]</scopeLabel> <validation> + <rule name="validate-number" xsi:type="boolean">true</rule> <rule name="validate-greater-than-zero" xsi:type="boolean">true</rule> </validation> <label translate="true">Maximum Qty Allowed in Shopping Cart</label> From 931af1d74c659912c5bad7477ca7070fb1488f60 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Mon, 2 Sep 2019 17:13:15 +0300 Subject: [PATCH 574/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- .../ActionGroup/AdminProductActionGroup.xml | 4 +- .../Test/Mftf/Page/AdminProductCreatePage.xml | 1 - .../ActionGroup/AdminProductActionGroup.xml | 21 +++++ .../Mftf/Data/CatalogInventoryConfigData.xml | 13 +++ .../Data/CatalogInventoryItemOptionsData.xml | 24 ++++++ .../Mftf/Data/CatalogInventryConfigData.xml | 24 ------ .../cataloginventory_item_options-meta.xml | 21 +++++ ...InventoryProductStockOptionsConfigPage.xml | 14 ++++ .../Test/Mftf/Page/AdminProductCreatePage.xml | 14 ++++ ...entoryProductStockOptionsConfigSection.xml | 16 ++++ ...minProductFormAdvancedInventorySection.xml | 2 + .../Mftf/Section/AdminProductFormSection.xml | 14 ++++ ...eroMaximumQtyAllowedInShoppingCartTest.xml | 83 +++++++++++++++++++ .../AdminSaveConfigActionGroup.xml | 4 +- 14 files changed, 226 insertions(+), 29 deletions(-) create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml delete mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventryConfigData.xml create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Metadata/cataloginventory_item_options-meta.xml create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminInventoryProductStockOptionsConfigPage.xml create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminProductCreatePage.xml create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminInventoryProductStockOptionsConfigSection.xml rename app/code/Magento/{Catalog => CatalogInventory}/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml (91%) create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormSection.xml create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index 0bb73e7416b07..a2588d6cb908b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -134,8 +134,8 @@ <scrollToTopOfPage stepKey="scrollTopPageProduct"/> <waitForElementVisible selector="{{AdminProductFormActionSection.saveButton}}" stepKey="waitForSaveProductButton"/> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> - <waitForPageLoad stepKey="waitForProductToSave"/> - <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." stepKey="seeSaveConfirmation"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitProductSaveSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the product." stepKey="seeSaveConfirmation"/> </actionGroup> <actionGroup name="toggleProductEnabled"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml index e4c4ece5ac6cf..5c7cb4d51084f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml @@ -18,7 +18,6 @@ <section name="AdminProductAttributesSection"/> <section name="AdminProductFormRelatedUpSellCrossSellSection"/> <section name="AdminProductFormAdvancedPricingSection"/> - <section name="AdminProductFormAdvancedInventorySection"/> <section name="AdminAddAttributeModalSection"/> </page> </pages> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml new file mode 100644 index 0000000000000..b4694365caff7 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminProductSetMaxQtyAllowedInShoppingCart"> + <arguments> + <argument name="qty" type="string"/> + </arguments> + <conditionalClick selector="{{AdminProductFormSection.advancedInventoryLink}}" dependentSelector="{{AdminProductFormAdvancedInventorySection.advancedInventoryModal}}" visible="false" stepKey="clickOnAdvancedInventoryLinkIfNeeded"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyConfigSetting}}" stepKey="waitForAdvancedInventoryModalWindowOpen"/> + <uncheckOption selector="{{AdminProductFormAdvancedInventorySection.maxiQtyConfigSetting}}" stepKey="uncheckMaxQtyCheckBox"/> + <fillField selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCart}}" userInput="{{qty}}" stepKey="fillMaxAllowedQty"/> + <click selector="{{AdminSlideOutDialogSection.doneButton}}" stepKey="clickDone"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml index e14c36446fc2b..cd5a8cf5bbac9 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryConfigData.xml @@ -20,4 +20,17 @@ <data key="label">No</data> <data key="value">0</data> </entity> + <entity name="EnableCatalogInventoryConfigData"> + <!--Default Value --> + <data key="path">cataloginventory/options/can_subtract</data> + <data key="scope_id">0</data> + <data key="label">Yes</data> + <data key="value">1</data> + </entity> + <entity name="DisableCatalogInventoryConfigData"> + <data key="path">cataloginventory/options/can_subtract</data> + <data key="scope_id">0</data> + <data key="label">No</data> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml new file mode 100644 index 0000000000000..912218e63abbe --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ZeroValueForMaxSaleQty" type="cataloginventory_item_options"> + <requiredEntity type="max_sale_qty">MaxSaleQtyZeroValue</requiredEntity> + </entity> + <entity name="MaxSaleQtyZeroValue" type="max_sale_qty"> + <data key="value">0</data> + </entity> + + <entity name="DefaultValueForMaxSaleQty" type="cataloginventory_item_options"> + <requiredEntity type="max_sale_qty">MaxSaleQtyDefaultValue</requiredEntity> + </entity> + <entity name="MaxSaleQtyDefaultValue" type="max_sale_qty"> + <data key="value">10000</data> + </entity> +</entities> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventryConfigData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventryConfigData.xml deleted file mode 100644 index 3a49b821ead5f..0000000000000 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventryConfigData.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="EnableCatalogInventoryConfigData"> - <!--Default Value --> - <data key="path">cataloginventory/options/can_subtract</data> - <data key="scope_id">0</data> - <data key="label">Yes</data> - <data key="value">1</data> - </entity> - <entity name="DisableCatalogInventoryConfigData"> - <data key="path">cataloginventory/options/can_subtract</data> - <data key="scope_id">0</data> - <data key="label">No</data> - <data key="value">0</data> - </entity> -</entities> \ No newline at end of file diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Metadata/cataloginventory_item_options-meta.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Metadata/cataloginventory_item_options-meta.xml new file mode 100644 index 0000000000000..7672cb7478f1a --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Metadata/cataloginventory_item_options-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CatalogInventoryProductStockOptionsConfiguration" dataType="cataloginventory_item_options" type="create" + auth="adminFormKey" url="/admin/system_config/save/section/cataloginventory/" method="POST" successRegex="/messages-message-success/"> + <object key="groups" dataType="cataloginventory_item_options"> + <object key="item_options" dataType="cataloginventory_item_options"> + <object key="fields" dataType="cataloginventory_item_options"> + <object key="max_sale_qty" dataType="max_sale_qty"> + <field key="value">integer</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminInventoryProductStockOptionsConfigPage.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminInventoryProductStockOptionsConfigPage.xml new file mode 100644 index 0000000000000..3d8c3ef3cf9f8 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminInventoryProductStockOptionsConfigPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminInventoryProductStockOptionsConfigPage" url="admin/system_config/edit/section/cataloginventory/#cataloginventory_item_options-link" area="admin" module="Magento_Config"> + <section name="AdminInventoryProductStockOptionsConfigSection"/> + </page> +</pages> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminProductCreatePage.xml new file mode 100644 index 0000000000000..5835e7564c172 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Page/AdminProductCreatePage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminProductCreatePage" url="catalog/product/new/set/{{set}}/type/{{type}}/" area="admin" module="Magento_Catalog" parameterized="true"> + <section name="AdminProductFormAdvancedInventorySection"/> + </page> +</pages> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminInventoryProductStockOptionsConfigSection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminInventoryProductStockOptionsConfigSection.xml new file mode 100644 index 0000000000000..ef7fe30f4970b --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminInventoryProductStockOptionsConfigSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminInventoryProductStockOptionsConfigSection"> + <element name="maxSaleQtyInherit" type="checkbox" selector="#cataloginventory_item_options_max_sale_qty_inherit" timeout="30"/> + <element name="maxSaleQty" type="input" selector="#cataloginventory_item_options_max_sale_qty"/> + <element name="maxSaleQtyError" type="input" selector="#cataloginventory_item_options_max_sale_qty-error"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml similarity index 91% rename from app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml rename to app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml index 4196a86fe25db..cdcd53144e70b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml @@ -30,5 +30,7 @@ <element name="advancedInventoryStockStatus" type="select" selector="//div[@class='modal-inner-wrap']//select[@name='product[quantity_and_stock_status][is_in_stock]']"/> <element name="outOfStockThreshold" type="select" selector="//*[@name='product[stock_data][min_qty]']" timeout="30"/> <element name="minQtyConfigSetting" type="checkbox" selector="//input[@name='product[stock_data][use_config_min_qty]']" timeout="30"/> + <element name="advancedInventoryModal" type="block" selector=".product_form_product_form_advanced_inventory_modal[data-role=modal]"/> + <element name="maxiQtyAllowedInCartError" type="text" selector="//*[@name='product[stock_data][max_sale_qty]']/..//label[@class='admin__field-error']"/> </section> </sections> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormSection.xml new file mode 100644 index 0000000000000..f4b79b17b3fc3 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminProductFormSection"> + <element name="advancedInventoryLink" type="button" selector="button[data-index='advanced_inventory_button']" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml new file mode 100644 index 0000000000000..6ad7e4dab16f4 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest"> + <annotations> + <features value="CatalogInventory"/> + <stories value="Sales restrictions"/> + <title value="Verify that product maximum qty allowed in shopping cart can't be set to zero or less"/> + <description value="Verify that product maximum qty allowed in shopping cart can't be set to zero or less"/> + <severity value="MAJOR"/> + <useCaseId value="MC-17606"/> + <testCaseId value="MC-17636"/> + <group value="catalog"/> + <group value="catalogInventory"/> + </annotations> + <before> + <createData entity="DefaultValueForMaxSaleQty" stepKey="setDefaultValueForMaxSaleQty"/> + <createData entity="SimpleProduct2" stepKey="createdProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <createData entity="DefaultValueForMaxSaleQty" stepKey="setDefaultValueForMaxSaleQty"/> + <deleteData createDataKey="createdProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{AdminInventoryProductStockOptionsConfigPage.url}}" stepKey="openInventoryConfigPage"/> + <uncheckOption selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyInherit}}" stepKey="uncheckUseDefaultValueForMaxSaleQty"/> + <!-- Validate zero value --> + <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="0" stepKey="setMaxSaleQtyValueToZero"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButton"/> + <waitForElementVisible selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" stepKey="waitValidationErrorMessageAppearsZero"/> + <see selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkValidationErrorMessageZero"/> + <!-- Validate negative value --> + <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="-1" stepKey="setMaxSaleQtyValueToNegative"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButtonNegative"/> + <waitForElementVisible selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" stepKey="waitValidationErrorMessageAppearsNegative"/> + <see selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkValidationErrorMessageNegative"/> + <!-- Validate alphabetical value --> + <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="abc" stepKey="setMaxSaleQtyValueToAlphabetical"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButtonAlphabetical"/> + <waitForElementVisible selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" stepKey="waitValidationErrorMessageAppearsAlphabetical"/> + <see selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" userInput="Please enter a valid number in this field." stepKey="checkValidationErrorMessageAlphabetical"/> + <!-- Fill correct value --> + <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="100" stepKey="setMaxSaleQtyValueToCorrectNumber"/> + <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfigWithCorrectNumber"/> + + <!-- Go to product page --> + <amOnPage url="{{AdminProductEditPage.url($$createdProduct.id$$)}}" stepKey="openAdminProductEditPage"/> + <!-- Validate zero value --> + <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToZero"> + <argument name="qty" value="0"/> + </actionGroup> + <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" stepKey="waitProductValidationErrorMessageAppearsZero"/> + <see selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkProductValidationErrorMessageZero"/> + <!-- Validate negative value --> + <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToNegative"> + <argument name="qty" value="-1"/> + </actionGroup> + <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" stepKey="waitProductValidationErrorMessageAppearsNegative"/> + <see selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkProductValidationErrorMessageNegative"/> + <!-- Validate alphabetical value --> + <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToAlphabetical"> + <argument name="qty" value="abc"/> + </actionGroup> + <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" stepKey="waitProductValidationErrorMessageAppearsAlphabetical"/> + <see selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" userInput="Please enter a valid number in this field." stepKey="checkProductValidationErrorMessageAlphabetical"/> + + <!-- Fill correct value --> + <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToCorrectNumber"> + <argument name="qty" value="50"/> + </actionGroup> + <waitForElementNotVisible selector="{{AdminProductFormAdvancedInventorySection.advancedInventoryModal}}" stepKey="waitForModalFormToDisappear"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + </test> +</tests> diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSaveConfigActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSaveConfigActionGroup.xml index 6ed0cfe95cb94..bd23292d3ee6a 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSaveConfigActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/AdminSaveConfigActionGroup.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminSaveConfigActionGroup"> <click selector="{{AdminConfigSection.saveButton}}" stepKey="clickSaveConfigBtn"/> - <waitForPageLoad stepKey="waitForPageLoad"/> - <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeSuccessMessage"/> </actionGroup> </actionGroups> From 03a5fed6de6efa90c958057d5acbbcbe495f9707 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Mon, 2 Sep 2019 18:15:07 +0300 Subject: [PATCH 575/841] MC-19585: CMS page redirects to homepage after changing store view --- ...age_with_same_url_for_different_stores.php | 31 +++++++++++++++++++ ...same_url_for_different_stores_rollback.php | 29 +++++++++++++++++ .../Model/StoreSwitcher/RewriteUrlTest.php | 29 ++--------------- 3 files changed, 63 insertions(+), 26 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php create mode 100644 dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores_rollback.php diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php b/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php new file mode 100644 index 0000000000000..8d6a85a624857 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../../Magento/Store/_files/second_store.php'; + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var $page \Magento\Cms\Model\Page */ +$page = $objectManager->create(\Magento\Cms\Model\Page::class); +$page->setTitle('First test page') + ->setIdentifier('page1') + ->setStores([1]) + ->setIsActive(1) + ->setPageLayout('1column') + ->save(); + +$storeManager = $objectManager->create(\Magento\Store\Model\StoreManagerInterface::class); +$store = $storeManager->getStore('fixture_second_store'); +/** @var $page \Magento\Cms\Model\Page */ +$page = $objectManager->create(\Magento\Cms\Model\Page::class); +$page->setTitle('Second test page') + ->setIdentifier('page1') + ->setStores([$store->getId()]) + ->setIsActive(1) + ->setPageLayout('1column') + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores_rollback.php b/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores_rollback.php new file mode 100644 index 0000000000000..ec7a422805172 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores_rollback.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Cms\Api\Data\PageInterface; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var PageRepositoryInterface $pageRepository */ +$pageRepository = $objectManager->get(PageRepositoryInterface::class); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter(PageInterface::IDENTIFIER, 'page1') + ->create(); +$result = $pageRepository->getList($searchCriteria); + +foreach ($result->getItems() as $item) { + $pageRepository->delete($item); +} + +// phpcs:ignore Magento2.Security.IncludeFile +require __DIR__ . '/../../../Magento/Store/_files/second_store_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php index 800f7e53bd68b..8ea9fdcd744f1 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php @@ -8,7 +8,6 @@ namespace Magento\UrlRewrite\Model\StoreSwitcher; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Cms\Model\Page; use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\App\Config\Value; use Magento\Store\Api\Data\StoreInterface; @@ -104,15 +103,12 @@ public function testSwitchToExistingPage(): void /** * Testing store switching using cms pages with the same url_key but with different page_id * - * @magentoDataFixture Magento/Cms/_files/pages.php - * @magentoDataFixture Magento/Store/_files/second_store.php + * @magentoDataFixture Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php * @magentoDbIsolation disabled * @return void */ public function testSwitchCmsPageToAnotherStore(): void { - $storeId = (int)$this->storeManager->getStore('fixture_second_store')->getId(); - $this->createCmsPage($storeId); $fromStore = $this->getStoreByCode('default'); $toStore = $this->getStoreByCode('fixture_second_store'); $redirectUrl = "http://localhost/index.php/page100/"; @@ -151,26 +147,7 @@ private function setBaseUrl(StoreInterface $targetStore): void */ private function getStoreByCode(string $storeCode): StoreInterface { - /** @var StoreRepositoryInterface $storeRepository */ - $storeRepository = $this->objectManager->create(StoreRepositoryInterface::class); - return $storeRepository->get($storeCode); - } - - /** - * Create cms page for store with store id from parameters - * - * @param int $storeId - * @return void - */ - private function createCmsPage(int $storeId): void - { - /** @var $page \Magento\Cms\Model\Page */ - $page = $this->objectManager->create(Page::class); - $page->setTitle('Test cms page') - ->setIdentifier('page100') - ->setStores([$storeId]) - ->setIsActive(1) - ->setPageLayout('1column') - ->save(); + /** @var StoreRepositoryInterface */ + return $this->storeManager->getStore($storeCode); } } From eeb60b0ec5b5dc9df7b2f2e03adffefa1dc37a8a Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Mon, 2 Sep 2019 18:42:52 +0300 Subject: [PATCH 576/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- .../Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index a2588d6cb908b..96c2c2900dccf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -152,6 +152,7 @@ <description>EXTENDS: saveProductForm. Removes 'seeSaveConfirmation'.</description> </annotations> + <remove keyForRemoval="waitProductSaveSuccessMessage"/> <remove keyForRemoval="seeSaveConfirmation"/> </actionGroup> From 30b1cc5a8d8af027c81bb14adbbed23be264dc43 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Mon, 2 Sep 2019 12:20:07 -0500 Subject: [PATCH 577/841] Update Message.php --- app/code/Magento/GiftMessage/Helper/Message.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/GiftMessage/Helper/Message.php b/app/code/Magento/GiftMessage/Helper/Message.php index ee0a29ef8f065..f824c9454d2c1 100644 --- a/app/code/Magento/GiftMessage/Helper/Message.php +++ b/app/code/Magento/GiftMessage/Helper/Message.php @@ -120,6 +120,8 @@ public function getInline($type, \Magento\Framework\DataObject $entity, $dontDis } /** + * Skip page by page type + * * @param string $pageType * @return bool */ From 1e0db4d659a4c5ac318db971db2fe666a72bd8a2 Mon Sep 17 00:00:00 2001 From: Sergey Dovbenko <sdovbenko@magecom.us> Date: Mon, 2 Sep 2019 19:39:14 +0000 Subject: [PATCH 578/841] Used @magentoConfigFixture + deleted customer_subscribe.php, customer_subscribe_rollback.php --- .../GraphQl/Customer/CreateCustomerTest.php | 2 +- .../Customer/_files/customer_subscribe.php | 13 ------------- .../_files/customer_subscribe_rollback.php | 16 ---------------- 3 files changed, 1 insertion(+), 30 deletions(-) delete mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php delete mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php index 5ebf76f3b6cc0..c5714012f38c9 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -276,7 +276,7 @@ public function testCreateCustomerIfNameEmpty() } /** - * @magentoApiDataFixture Magento/Customer/_files/customer_subscribe.php + * @magentoConfigFixture default_store newsletter/general/active 0 */ public function testCreateCustomerSubscribed() { diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php deleted file mode 100644 index 58eb11fb00e8d..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe.php +++ /dev/null @@ -1,13 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -$resourceConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Config\Model\ResourceModel\Config::class); -$resourceConfig->saveConfig( - 'newsletter/general/active', - false, - 'default', - 0 -); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php deleted file mode 100644 index 26823c7534b99..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_subscribe_rollback.php +++ /dev/null @@ -1,16 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167 - -declare(strict_types=1); - -use Magento\Framework\App\Config\Storage\WriterInterface; -use Magento\TestFramework\Helper\Bootstrap; - -$objectManager = Bootstrap::getObjectManager(); -/** @var Writer $configWriter */ -$configWriter = $objectManager->create(WriterInterface::class); -$configWriter->delete('newsletter/general/active'); From e3e81fc227eb6845ade2f78e2d5addb308e6a563 Mon Sep 17 00:00:00 2001 From: Sergii Ivashchenko <serg.ivashchenko@gmail.com> Date: Mon, 2 Sep 2019 21:04:07 +0100 Subject: [PATCH 579/841] magento/magento2#23600: Corrected the comment --- app/code/Magento/Config/etc/acl.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/Config/etc/acl.xml b/app/code/Magento/Config/etc/acl.xml index 922cb5facb805..ff6ac2f50253a 100644 --- a/app/code/Magento/Config/etc/acl.xml +++ b/app/code/Magento/Config/etc/acl.xml @@ -13,8 +13,7 @@ <resource id="Magento_Backend::stores_settings"> <resource id="Magento_Config::config" title="Configuration" translate="title" sortOrder="20"> <!-- - @deprecated Magento does not support custom disabling/enabling modules output since 2.2.0 version. - Section 'Advanced Section' was disabled. This section will be removed from code in one release. + @deprecated Magento_Config::advanced section ('Advanced Section') is disabled and is not displayed in the admin panel --> <resource id="Magento_Config::advanced" title="Advanced Section" translate="title" sortOrder="90" disabled="true" /> <resource id="Magento_Config::config_admin" title="Advanced Admin Section" translate="title" sortOrder="100" /> From 29a1544c59766e6d807db18daa941badf86a3d95 Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Tue, 3 Sep 2019 06:13:25 +0530 Subject: [PATCH 580/841] change label into transate csv --- app/code/Magento/Checkout/i18n/en_US.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Checkout/i18n/en_US.csv b/app/code/Magento/Checkout/i18n/en_US.csv index 7f2f0b4390321..251985faf6cc4 100644 --- a/app/code/Magento/Checkout/i18n/en_US.csv +++ b/app/code/Magento/Checkout/i18n/en_US.csv @@ -156,8 +156,8 @@ Shipping,Shipping "Number of Items to Display Pager","Number of Items to Display Pager" "My Cart Link","My Cart Link" "Display Cart Summary","Display Cart Summary" -"Shopping Cart Sidebar","Shopping Cart Sidebar" -"Display Shopping Cart Sidebar","Display Shopping Cart Sidebar" +"Mini Cart","Mini Cart" +"Display Mini Cart","Display Mini Cart" "Number of Items to Display Scrollbar","Number of Items to Display Scrollbar" "Maximum Number of Items to Display","Maximum Number of Items to Display" "Payment Failed Emails","Payment Failed Emails" From b5ede75880b8fb9cc177b5aa16bf368ff32ff6bb Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Tue, 3 Sep 2019 09:04:43 +0300 Subject: [PATCH 581/841] MC-19585: CMS page redirects to homepage after changing store view --- .../_files/two_cms_page_with_same_url_for_different_stores.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php b/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php index 8d6a85a624857..b633c310a797b 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php +++ b/dev/tests/integration/testsuite/Magento/Cms/_files/two_cms_page_with_same_url_for_different_stores.php @@ -19,8 +19,6 @@ ->setPageLayout('1column') ->save(); -$storeManager = $objectManager->create(\Magento\Store\Model\StoreManagerInterface::class); -$store = $storeManager->getStore('fixture_second_store'); /** @var $page \Magento\Cms\Model\Page */ $page = $objectManager->create(\Magento\Cms\Model\Page::class); $page->setTitle('Second test page') From b027f7f5e4f6969455ac4766df926ffcbedae556 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Tue, 3 Sep 2019 09:41:34 +0300 Subject: [PATCH 582/841] MC-19537: Setting page size to "All" doesn't work with Elastic Search --- .../Collection/SearchCriteriaResolver.php | 4 ++- .../Collection/SearchCriteriaResolverTest.php | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 255c7885e84b9..1c8885fec63be 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -76,7 +76,9 @@ public function __construct( */ public function resolve(): SearchCriteria { - $this->builder->setPageSize($this->size); + if ($this->size !== 0) { + $this->builder->setPageSize($this->size); + } $searchCriteria = $this->builder->create(); $searchCriteria->setRequestName($this->searchRequestName); $searchCriteria->setSortOrders($this->orders); diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php index 30a1642378b71..fbf63630464f9 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php @@ -34,15 +34,18 @@ protected function setUp() } /** - * @param array|null $orders - * @param array|null $expected + * @param array $params + * @param array $expected * @dataProvider resolveSortOrderDataProvider */ - public function testResolve($orders, $expected) + public function testResolve($params, $expected) { $searchRequestName = 'test'; $currentPage = 1; - $size = 10; + $size = $params['size']; + $expectedSize = $expected['size']; + $orders = $params['orders']; + $expectedOrders = $expected['orders']; $searchCriteria = $this->getMockBuilder(SearchCriteria::class) ->disableOriginalConstructor() @@ -54,7 +57,7 @@ public function testResolve($orders, $expected) ->willReturn($searchCriteria); $searchCriteria->expects($this->once()) ->method('setSortOrders') - ->with($expected) + ->with($expectedOrders) ->willReturn($searchCriteria); $searchCriteria->expects($this->once()) ->method('setCurrentPage') @@ -64,10 +67,16 @@ public function testResolve($orders, $expected) $this->searchCriteriaBuilder->expects($this->once()) ->method('create') ->willReturn($searchCriteria); - $this->searchCriteriaBuilder->expects($this->once()) - ->method('setPageSize') - ->with($size) - ->willReturn($this->searchCriteriaBuilder); + + if ($expectedSize === null) { + $this->searchCriteriaBuilder->expects($this->never()) + ->method('setPageSize'); + } else { + $this->searchCriteriaBuilder->expects($this->once()) + ->method('setPageSize') + ->with($expectedSize) + ->willReturn($this->searchCriteriaBuilder); + } $objectManager = new ObjectManagerHelper($this); /** @var SearchCriteriaResolver $model */ @@ -92,12 +101,12 @@ public function resolveSortOrderDataProvider() { return [ [ - null, - null, + ['size' => 0, 'orders' => null], + ['size' => null, 'orders' => null], ], [ - ['test' => 'ASC'], - ['test' => 'ASC'], + ['size' => 10, 'orders' => ['test' => 'ASC']], + ['size' => 10, 'orders' => ['test' => 'ASC']], ], ]; } From 8ca33f1f5f808e32c41ba4cffa5c3519addf3c89 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Tue, 3 Sep 2019 12:49:07 +0400 Subject: [PATCH 583/841] MAGETWO-98748: Incorrect behavior in the category menu on the Storefront - Updated automated test script --- ...tCategoryHighlightedAndProductDisplayedTest.xml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml index c036712398bf9..3e72df9133898 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryHighlightedAndProductDisplayedTest.xml @@ -15,7 +15,7 @@ <title value="Сheck that current category is highlighted and all products displayed for it"/> <description value="Сheck that current category is highlighted and all products displayed for it"/> <severity value="MAJOR"/> - <testCaseId value="MAGETWO-99028"/> + <testCaseId value="MC-19626"/> <useCaseId value="MAGETWO-98748"/> <group value="Catalog"/> </annotations> @@ -31,10 +31,18 @@ <createData entity="SimpleProduct" stepKey="product2"> <requiredEntity createDataKey="category1"/> </createData> + <createData entity="SimpleProduct" stepKey="product3"> + <requiredEntity createDataKey="category2"/> + </createData> + <createData entity="SimpleProduct" stepKey="product4"> + <requiredEntity createDataKey="category2"/> + </createData> </before> <after> <deleteData createDataKey="product1" stepKey="deleteProduct1"/> <deleteData createDataKey="product2" stepKey="deleteProduct2"/> + <deleteData createDataKey="product3" stepKey="deleteProduct3"/> + <deleteData createDataKey="product4" stepKey="deleteProduct4"/> <deleteData createDataKey="category1" stepKey="deleteCategory1"/> <deleteData createDataKey="category2" stepKey="deleteCategory2"/> <deleteData createDataKey="category3" stepKey="deleteCategory3"/> @@ -69,5 +77,9 @@ <assertContains expectedType="string" expected="active" actual="$grabCategory2Class" stepKey="assertCategory2IsHighlighted"/> <executeJS function="return document.querySelectorAll('{{AdminCategorySidebarTreeSection.categoryNotHighlighted}}').length" stepKey="highlightedAmount2"/> <assertEquals expectedType="int" expected="1" actual="$highlightedAmount2" stepKey="assertRestCategories1IsNotHighlighted2"/> + <!--Assert products in second category page--> + <comment userInput="Assert products in second category page" stepKey="commentAssertProducts"/> + <seeElement selector="{{StorefrontCategoryMainSection.specifiedProductItemInfo($product3.name$)}}" stepKey="seeProduct3InCategoryPage"/> + <seeElement selector="{{StorefrontCategoryMainSection.specifiedProductItemInfo($product4.name$)}}" stepKey="seeProduct4InCategoryPage"/> </test> </tests> From aa7ac82dfd96e4410695cb0cda1bb44e0c65b0a4 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Tue, 3 Sep 2019 12:27:24 +0300 Subject: [PATCH 584/841] magento/magento2#22478: MFTF test fix. --- .../NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml index 1f3d9db5ca524..3c98f9177f4a7 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml @@ -47,7 +47,8 @@ <see selector="{{StoreFrontRemoveItemModalSection.message}}" userInput="Are you sure you would like to remove this item from the shopping cart?" stepKey="seeDeleteConfirmationMessage"/> <click selector="{{StoreFrontRemoveItemModalSection.ok}}" stepKey="confirmDelete"/> <waitForPageLoad stepKey="waitForDeleteToFinish"/> - <click selector="{{CheckoutCartProductSection.RemoveItem}}" stepKey="deleteProductFromCheckoutCart"/> + <dontSeeElement selector="{{CheckoutCartProductSection.RemoveItem}}" stepKey="dontSeeDeleteProductFromCheckoutCart"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> <waitForPageLoad stepKey="WaitForPageLoad3"/> <see userInput="You have no items in your shopping cart." stepKey="seeNoItemsInShoppingCart"/> </test> From 2ac7ada06a077c7d47cb38b035ef40a8a75402f0 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Tue, 3 Sep 2019 12:35:37 +0300 Subject: [PATCH 585/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- ...sertStorefrontShoppingCartSummaryWithShippingActionGroup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml index e74f5c24fb4f6..d5ae9abf44676 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml @@ -16,9 +16,9 @@ <argument name="shipping" type="string"/> </arguments> - <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" stepKey="waitForElementToBeVisible" after="assertSubtotal"/> <reloadPage stepKey="reloadPage" after="waitForElementToBeVisible" /> <waitForPageLoad after="reloadPage" stepKey="WaitForPageLoaded" /> + <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" stepKey="waitForElementToBeVisible" after="assertSubtotal"/> <waitForText userInput="{{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" time="30" stepKey="assertShipping" after="WaitForPageLoaded"/> </actionGroup> </actionGroups> From dc5f0f5b6f3ccfe28d3b612184e59786125bfedf Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Tue, 3 Sep 2019 14:32:00 +0300 Subject: [PATCH 586/841] magento/magento2#24254: MFTF test fix. --- .../Catalog/view/frontend/web/js/catalog-add-to-cart.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js index fc4809d381967..382b4ef98532b 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js @@ -135,7 +135,9 @@ define([ // trigger global event, so other modules will be able add parameters to redirect url $('body').trigger('catalogCategoryAddToCartRedirect', eventData); - if (eventData.redirectParameters.length > 0 && window.location.href === res.backUrl) { + if (eventData.redirectParameters.length > 0 && + window.location.href.split(/[?#]/)[0] === res.backUrl + ) { parameters = res.backUrl.split('#'); parameters.push(eventData.redirectParameters.join('&')); res.backUrl = parameters.join('#'); From a15f822811cf19b7673dc05ac7c110380730cc58 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Tue, 3 Sep 2019 16:06:03 +0300 Subject: [PATCH 587/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- .../ActionGroup/AdminProductActionGroup.xml | 2 +- ...talogInventoryConfigurationActionGroup.xml | 22 ++++++++++ .../ActionGroup/AdminProductActionGroup.xml | 10 +++++ .../Data/CatalogInventoryItemOptionsData.xml | 7 ---- ...minProductFormAdvancedInventorySection.xml | 2 +- ...eroMaximumQtyAllowedInShoppingCartTest.xml | 41 +++++++++---------- ...pingCartSummaryWithShippingActionGroup.xml | 6 +-- 7 files changed, 55 insertions(+), 35 deletions(-) create mode 100644 app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryConfigurationActionGroup.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index 96c2c2900dccf..5c5ee0f9cb321 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -149,7 +149,7 @@ <!-- Save product but do not expect a success message --> <actionGroup name="SaveProductFormNoSuccessCheck" extends="saveProductForm"> <annotations> - <description>EXTENDS: saveProductForm. Removes 'seeSaveConfirmation'.</description> + <description>EXTENDS: saveProductForm. Removes 'waitProductSaveSuccessMessage' and 'seeSaveConfirmation'.</description> </annotations> <remove keyForRemoval="waitProductSaveSuccessMessage"/> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryConfigurationActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryConfigurationActionGroup.xml new file mode 100644 index 0000000000000..49956473132ec --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryConfigurationActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCatalogInventoryConfigurationMaxQtyAllowedInShoppingCartValidationActionGroup"> + <arguments> + <argument name="qty" type="string"/> + <argument name="errorMessage" type="string"/> + </arguments> + + <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="{{qty}}" stepKey="setMaxSaleQtyValue"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButton"/> + <waitForElementVisible selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" stepKey="waitValidationErrorMessageAppears"/> + <see selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" userInput="{{errorMessage}}" stepKey="checkValidationErrorMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index b4694365caff7..84dc6b93c885f 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -18,4 +18,14 @@ <fillField selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCart}}" userInput="{{qty}}" stepKey="fillMaxAllowedQty"/> <click selector="{{AdminSlideOutDialogSection.doneButton}}" stepKey="clickDone"/> </actionGroup> + + <actionGroup name="AdminProductMaxQtyAllowedInShoppingCartValidationActionGroup" extends="AdminProductSetMaxQtyAllowedInShoppingCart"> + <arguments> + <argument name="qty" type="string"/> + <argument name="errorMessage" type="string"/> + </arguments> + + <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" after="clickDone" stepKey="waitProductValidationErrorMessageAppears"/> + <see selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" userInput="{{errorMessage}}" after="waitProductValidationErrorMessageAppears" stepKey="checkProductValidationErrorMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml index 912218e63abbe..767d65f9facca 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Data/CatalogInventoryItemOptionsData.xml @@ -8,13 +8,6 @@ <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="ZeroValueForMaxSaleQty" type="cataloginventory_item_options"> - <requiredEntity type="max_sale_qty">MaxSaleQtyZeroValue</requiredEntity> - </entity> - <entity name="MaxSaleQtyZeroValue" type="max_sale_qty"> - <data key="value">0</data> - </entity> - <entity name="DefaultValueForMaxSaleQty" type="cataloginventory_item_options"> <requiredEntity type="max_sale_qty">MaxSaleQtyDefaultValue</requiredEntity> </entity> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml index cdcd53144e70b..7ff9c2d70755f 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml @@ -31,6 +31,6 @@ <element name="outOfStockThreshold" type="select" selector="//*[@name='product[stock_data][min_qty]']" timeout="30"/> <element name="minQtyConfigSetting" type="checkbox" selector="//input[@name='product[stock_data][use_config_min_qty]']" timeout="30"/> <element name="advancedInventoryModal" type="block" selector=".product_form_product_form_advanced_inventory_modal[data-role=modal]"/> - <element name="maxiQtyAllowedInCartError" type="text" selector="//*[@name='product[stock_data][max_sale_qty]']/..//label[@class='admin__field-error']"/> + <element name="maxiQtyAllowedInCartError" type="text" selector="[name='product[stock_data][max_sale_qty]'] + label.admin__field-error"/> </section> </sections> diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml index 6ad7e4dab16f4..f7cf0a4deba4b 100644 --- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml +++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AdminCreateProductWithZeroMaximumQtyAllowedInShoppingCartTest.xml @@ -31,23 +31,24 @@ <actionGroup ref="logout" stepKey="logout"/> </after> + <!-- Go to Inventory configuration page --> <amOnPage url="{{AdminInventoryProductStockOptionsConfigPage.url}}" stepKey="openInventoryConfigPage"/> <uncheckOption selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyInherit}}" stepKey="uncheckUseDefaultValueForMaxSaleQty"/> <!-- Validate zero value --> - <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="0" stepKey="setMaxSaleQtyValueToZero"/> - <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButton"/> - <waitForElementVisible selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" stepKey="waitValidationErrorMessageAppearsZero"/> - <see selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkValidationErrorMessageZero"/> + <actionGroup ref="AdminCatalogInventoryConfigurationMaxQtyAllowedInShoppingCartValidationActionGroup" stepKey="validateZeroValue"> + <argument name="qty" value="0"/> + <argument name="errorMessage" value="Please enter a number greater than 0 in this field."/> + </actionGroup> <!-- Validate negative value --> - <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="-1" stepKey="setMaxSaleQtyValueToNegative"/> - <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButtonNegative"/> - <waitForElementVisible selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" stepKey="waitValidationErrorMessageAppearsNegative"/> - <see selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkValidationErrorMessageNegative"/> + <actionGroup ref="AdminCatalogInventoryConfigurationMaxQtyAllowedInShoppingCartValidationActionGroup" stepKey="validateNegativeValue"> + <argument name="qty" value="-1"/> + <argument name="errorMessage" value="Please enter a number greater than 0 in this field."/> + </actionGroup> <!-- Validate alphabetical value --> - <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="abc" stepKey="setMaxSaleQtyValueToAlphabetical"/> - <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveConfigButtonAlphabetical"/> - <waitForElementVisible selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" stepKey="waitValidationErrorMessageAppearsAlphabetical"/> - <see selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQtyError}}" userInput="Please enter a valid number in this field." stepKey="checkValidationErrorMessageAlphabetical"/> + <actionGroup ref="AdminCatalogInventoryConfigurationMaxQtyAllowedInShoppingCartValidationActionGroup" stepKey="validateAlphabeticalValue"> + <argument name="qty" value="abc"/> + <argument name="errorMessage" value="Please enter a valid number in this field."/> + </actionGroup> <!-- Fill correct value --> <fillField selector="{{AdminInventoryProductStockOptionsConfigSection.maxSaleQty}}" userInput="100" stepKey="setMaxSaleQtyValueToCorrectNumber"/> <actionGroup ref="AdminSaveConfigActionGroup" stepKey="saveConfigWithCorrectNumber"/> @@ -55,24 +56,20 @@ <!-- Go to product page --> <amOnPage url="{{AdminProductEditPage.url($$createdProduct.id$$)}}" stepKey="openAdminProductEditPage"/> <!-- Validate zero value --> - <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToZero"> + <actionGroup ref="AdminProductMaxQtyAllowedInShoppingCartValidationActionGroup" stepKey="productValidateZeroValue"> <argument name="qty" value="0"/> + <argument name="errorMessage" value="Please enter a number greater than 0 in this field."/> </actionGroup> - <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" stepKey="waitProductValidationErrorMessageAppearsZero"/> - <see selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkProductValidationErrorMessageZero"/> <!-- Validate negative value --> - <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToNegative"> + <actionGroup ref="AdminProductMaxQtyAllowedInShoppingCartValidationActionGroup" stepKey="productValidateNegativeValue"> <argument name="qty" value="-1"/> + <argument name="errorMessage" value="Please enter a number greater than 0 in this field."/> </actionGroup> - <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" stepKey="waitProductValidationErrorMessageAppearsNegative"/> - <see selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" userInput="Please enter a number greater than 0 in this field." stepKey="checkProductValidationErrorMessageNegative"/> <!-- Validate alphabetical value --> - <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToAlphabetical"> + <actionGroup ref="AdminProductMaxQtyAllowedInShoppingCartValidationActionGroup" stepKey="productValidateAlphabeticalValue"> <argument name="qty" value="abc"/> + <argument name="errorMessage" value="Please enter a valid number in this field."/> </actionGroup> - <waitForElementVisible selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" stepKey="waitProductValidationErrorMessageAppearsAlphabetical"/> - <see selector="{{AdminProductFormAdvancedInventorySection.maxiQtyAllowedInCartError}}" userInput="Please enter a valid number in this field." stepKey="checkProductValidationErrorMessageAlphabetical"/> - <!-- Fill correct value --> <actionGroup ref="AdminProductSetMaxQtyAllowedInShoppingCart" stepKey="setProductMaxQtyAllowedInShoppingCartToCorrectNumber"> <argument name="qty" value="50"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml index d5ae9abf44676..0ba739e415fb7 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml @@ -16,9 +16,7 @@ <argument name="shipping" type="string"/> </arguments> - <reloadPage stepKey="reloadPage" after="waitForElementToBeVisible" /> - <waitForPageLoad after="reloadPage" stepKey="WaitForPageLoaded" /> - <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" stepKey="waitForElementToBeVisible" after="assertSubtotal"/> - <waitForText userInput="{{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" time="30" stepKey="assertShipping" after="WaitForPageLoaded"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" after="assertSubtotal" stepKey="waitForShippingElementToBeVisible"/> + <waitForText userInput="{{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" time="30" after="waitForShippingElementToBeVisible" stepKey="assertShipping"/> </actionGroup> </actionGroups> From ba3a134e1abcfc4365f22756735c3cf52b7269cb Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 3 Sep 2019 20:31:14 +0700 Subject: [PATCH 588/841] resolve {{config path='trans_email/ident_support/email'}} does not display support email issue24419 --- app/code/Magento/Variable/etc/di.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/Variable/etc/di.xml b/app/code/Magento/Variable/etc/di.xml index 41759e1f1582b..99f299e9197f1 100644 --- a/app/code/Magento/Variable/etc/di.xml +++ b/app/code/Magento/Variable/etc/di.xml @@ -21,6 +21,10 @@ <item name="trans_email/ident_sales/name" xsi:type="string">1</item> <item name="trans_email/ident_sales/email" xsi:type="string">1</item> </item> + <item name="trans_email/ident_support" xsi:type="array"> + <item name="trans_email/ident_support/name" xsi:type="string">1</item> + <item name="trans_email/ident_support/email" xsi:type="string">1</item> + </item> <item name="trans_email/ident_custom1" xsi:type="array"> <item name="trans_email/ident_custom1/name" xsi:type="string">1</item> <item name="trans_email/ident_custom1/email" xsi:type="string">1</item> From aaa737920ee5f0d7f2ae6b587d1321ebd2ffcb8f Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 3 Sep 2019 21:14:20 +0700 Subject: [PATCH 589/841] Move class StoreView to store module issue 24387 --- .../Cms/view/adminhtml/ui_component/cms_block_form.xml | 2 +- .../{Cms => Store}/Ui/Component/Form/Field/StoreView.php | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) rename app/code/Magento/{Cms => Store}/Ui/Component/Form/Field/StoreView.php (89%) diff --git a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml index 83e53f96e0653..ad0f33df59d4e 100644 --- a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml +++ b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_form.xml @@ -111,7 +111,7 @@ <dataScope>identifier</dataScope> </settings> </field> - <field name="storeviews" formElement="multiselect" class="Magento\Cms\Ui\Component\Form\Field\StoreView"> + <field name="storeviews" formElement="multiselect" class="Magento\Store\Ui\Component\Form\Field\StoreView"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="source" xsi:type="string">block</item> diff --git a/app/code/Magento/Cms/Ui/Component/Form/Field/StoreView.php b/app/code/Magento/Store/Ui/Component/Form/Field/StoreView.php similarity index 89% rename from app/code/Magento/Cms/Ui/Component/Form/Field/StoreView.php rename to app/code/Magento/Store/Ui/Component/Form/Field/StoreView.php index 3a7424bd35ee0..3113cee49abe2 100644 --- a/app/code/Magento/Cms/Ui/Component/Form/Field/StoreView.php +++ b/app/code/Magento/Store/Ui/Component/Form/Field/StoreView.php @@ -5,18 +5,17 @@ */ declare(strict_types=1); -namespace Magento\Cms\Ui\Component\Form\Field; +namespace Magento\Store\Ui\Component\Form\Field; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Framework\View\Element\UiComponentInterface; use Magento\Store\Model\StoreManagerInterface as StoreManager; use Magento\Ui\Component\Form\Field; /** * Check to disable store view field * - * Class \Magento\Cms\Ui\Component\Form\Field\StoreView + * Class \Magento\Store\Ui\Component\Form\Field\StoreView */ class StoreView extends Field { From 38cf8d61b3cca8d7e522f92601feccafbb0107b6 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Tue, 3 Sep 2019 21:59:51 +0700 Subject: [PATCH 590/841] Remove "Add Review" if System has no Product issue24310 --- .../Magento/Review/Block/Adminhtml/Main.php | 24 ++++++++- .../Test/Unit/Block/Adminhtml/MainTest.php | 49 ++++++++++++++----- 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Review/Block/Adminhtml/Main.php b/app/code/Magento/Review/Block/Adminhtml/Main.php index 45d14a1c60e69..5fae6acbce48a 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Main.php +++ b/app/code/Magento/Review/Block/Adminhtml/Main.php @@ -3,12 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); /** * Adminhtml review main block */ namespace Magento\Review\Block\Adminhtml; +use Magento\Framework\App\ObjectManager; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory; + +/** + * Class \Magento\Review\Block\Adminhtml\Main + */ class Main extends \Magento\Backend\Block\Widget\Grid\Container { /** @@ -37,6 +44,13 @@ class Main extends \Magento\Backend\Block\Widget\Grid\Container */ protected $_customerViewHelper; + /** + * Product Collection + * + * @var ProductCollectionFactory + */ + private $productCollectionFactory; + /** * @param \Magento\Backend\Block\Widget\Context $context * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository @@ -44,6 +58,7 @@ class Main extends \Magento\Backend\Block\Widget\Grid\Container * @param \Magento\Framework\Registry $registry * @param \Magento\Customer\Helper\View $customerViewHelper * @param array $data + * @param ProductCollectionFactory $productCollectionFactory */ public function __construct( \Magento\Backend\Block\Widget\Context $context, @@ -51,12 +66,15 @@ public function __construct( \Magento\Catalog\Model\ProductFactory $productFactory, \Magento\Framework\Registry $registry, \Magento\Customer\Helper\View $customerViewHelper, - array $data = [] + array $data = [], + ProductCollectionFactory $productCollectionFactory = null ) { $this->_coreRegistry = $registry; $this->customerRepository = $customerRepository; $this->_productFactory = $productFactory; $this->_customerViewHelper = $customerViewHelper; + $this->productCollectionFactory = $productCollectionFactory ?: ObjectManager::getInstance() + ->get(ProductCollectionFactory::class); parent::__construct($context, $data); } @@ -73,6 +91,10 @@ protected function _construct() $this->_blockGroup = 'Magento_Review'; $this->_controller = 'adminhtml'; + if (!$this->productCollectionFactory->create()->getSize()) { + $this->removeButton('add'); + } + // lookup customer, if id is specified $customerId = $this->getRequest()->getParam('customerId', false); $customerName = ''; diff --git a/app/code/Magento/Review/Test/Unit/Block/Adminhtml/MainTest.php b/app/code/Magento/Review/Test/Unit/Block/Adminhtml/MainTest.php index c7ff721d4ba22..41cabf68262d1 100644 --- a/app/code/Magento/Review/Test/Unit/Block/Adminhtml/MainTest.php +++ b/app/code/Magento/Review/Test/Unit/Block/Adminhtml/MainTest.php @@ -3,39 +3,59 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Review\Test\Unit\Block\Adminhtml; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Helper\View as ViewHelper; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\App\RequestInterface; +use Magento\Review\Block\Adminhtml\Main as MainBlock; +use Magento\Framework\DataObject; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +/** + * Unit Test For Main Block + * + * Class \Magento\Review\Test\Unit\Block\Adminhtml\MainTest + */ class MainTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Review\Block\Adminhtml\Main + * @var MainBlock */ protected $model; /** - * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $request; /** - * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $customerRepository; /** - * @var \Magento\Customer\Helper\View|\PHPUnit_Framework_MockObject_MockObject + * @var ViewHelper|\PHPUnit_Framework_MockObject_MockObject */ protected $customerViewHelper; + /** + * @var CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + protected $collectionFactory; + public function testConstruct() { $this->customerRepository = $this - ->getMockForAbstractClass(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $this->customerViewHelper = $this->createMock(\Magento\Customer\Helper\View::class); - $dummyCustomer = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + ->getMockForAbstractClass(CustomerRepositoryInterface::class); + $this->customerViewHelper = $this->createMock(ViewHelper::class); + $this->collectionFactory = $this->createMock(CollectionFactory::class); + $dummyCustomer = $this->getMockForAbstractClass(CustomerInterface::class); $this->customerRepository->expects($this->once()) ->method('getById') @@ -44,8 +64,8 @@ public function testConstruct() $this->customerViewHelper->expects($this->once()) ->method('getCustomerName') ->with($dummyCustomer) - ->will($this->returnValue(new \Magento\Framework\DataObject())); - $this->request = $this->getMockForAbstractClass(\Magento\Framework\App\RequestInterface::class); + ->will($this->returnValue(new DataObject())); + $this->request = $this->getMockForAbstractClass(RequestInterface::class); $this->request->expects($this->at(0)) ->method('getParam') ->with('customerId', false) @@ -54,14 +74,21 @@ public function testConstruct() ->method('getParam') ->with('productId', false) ->will($this->returnValue(false)); + $productCollection = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collectionFactory->expects($this->once()) + ->method('create') + ->will($this->returnValue($productCollection)); $objectManagerHelper = new ObjectManagerHelper($this); $this->model = $objectManagerHelper->getObject( - \Magento\Review\Block\Adminhtml\Main::class, + MainBlock::class, [ 'request' => $this->request, 'customerRepository' => $this->customerRepository, - 'customerViewHelper' => $this->customerViewHelper + 'customerViewHelper' => $this->customerViewHelper, + 'productCollectionFactory' => $this->collectionFactory ] ); } From 7d19fe02eaae1fdc824f26a068dc537211eed9d7 Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Fri, 30 Aug 2019 15:24:05 -0500 Subject: [PATCH 591/841] MC-19713: Relative Category links created using PageBuilder have the store name as a URL parameter - Remove store query param from category and product URL on cms pages --- .../Magento/Catalog/Block/Widget/Link.php | 19 +- .../Test/Unit/Block/Widget/LinkTest.php | 221 ++++++++++++------ 2 files changed, 158 insertions(+), 82 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Widget/Link.php b/app/code/Magento/Catalog/Block/Widget/Link.php index 85e50dbd3dc27..3d8a1cdf91ca2 100644 --- a/app/code/Magento/Catalog/Block/Widget/Link.php +++ b/app/code/Magento/Catalog/Block/Widget/Link.php @@ -4,17 +4,15 @@ * See COPYING.txt for license details. */ -/** - * Widget to display catalog link - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Catalog\Block\Widget; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +/** + * Render the URL of given entity + */ class Link extends \Magento\Framework\View\Element\Html\Link implements \Magento\Widget\Block\BlockInterface { /** @@ -63,10 +61,9 @@ public function __construct( /** * Prepare url using passed id path and return it - * or return false if path was not found in url rewrites. * * @throws \RuntimeException - * @return string|false + * @return string|false if path was not found in url rewrites. * @SuppressWarnings(PHPMD.NPathComplexity) */ public function getHref() @@ -92,10 +89,6 @@ public function getHref() if ($rewrite) { $href = $store->getUrl('', ['_direct' => $rewrite->getRequestPath()]); - - if (strpos($href, '___store') === false) { - $href .= (strpos($href, '?') === false ? '?' : '&') . '___store=' . $store->getCode(); - } } $this->_href = $href; } @@ -121,6 +114,7 @@ protected function parseIdPath($idPath) /** * Prepare label using passed text as parameter. + * * If anchor text was not specified get entity name from DB. * * @return string @@ -150,9 +144,8 @@ public function getLabel() /** * Render block HTML - * or return empty string if url can't be prepared * - * @return string + * @return string empty string if url can't be prepared */ protected function _toHtml() { diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php index 8333ed22e1da0..3ceaf7dd44f57 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php @@ -5,54 +5,81 @@ */ namespace Magento\Catalog\Test\Unit\Block\Widget; +use Exception; +use Magento\Catalog\Block\Widget\Link; +use Magento\Catalog\Model\ResourceModel\AbstractResource; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Url; +use Magento\Framework\Url\ModifierInterface; +use Magento\Framework\View\Element\Template\Context; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\UrlRewrite\Model\UrlFinderInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; +use ReflectionClass; +use RuntimeException; -class LinkTest extends \PHPUnit\Framework\TestCase +/** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class LinkTest extends TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\StoreManagerInterface + * @var PHPUnit_Framework_MockObject_MockObject|StoreManagerInterface */ protected $storeManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\UrlRewrite\Model\UrlFinderInterface + * @var PHPUnit_Framework_MockObject_MockObject|UrlFinderInterface */ protected $urlFinder; /** - * @var \Magento\Catalog\Block\Widget\Link + * @var Link */ protected $block; /** - * @var \Magento\Catalog\Model\ResourceModel\AbstractResource|\PHPUnit_Framework_MockObject_MockObject + * @var AbstractResource|PHPUnit_Framework_MockObject_MockObject */ protected $entityResource; + /** + * @inheritDoc + */ protected function setUp() { - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->urlFinder = $this->createMock(\Magento\UrlRewrite\Model\UrlFinderInterface::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->urlFinder = $this->createMock(UrlFinderInterface::class); - $context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class); + $context = $this->createMock(Context::class); $context->expects($this->any()) ->method('getStoreManager') ->will($this->returnValue($this->storeManager)); $this->entityResource = - $this->createMock(\Magento\Catalog\Model\ResourceModel\AbstractResource::class); - - $this->block = (new ObjectManager($this))->getObject(\Magento\Catalog\Block\Widget\Link::class, [ - 'context' => $context, - 'urlFinder' => $this->urlFinder, - 'entityResource' => $this->entityResource - ]); + $this->createMock(AbstractResource::class); + + $this->block = (new ObjectManager($this))->getObject( + Link::class, + [ + 'context' => $context, + 'urlFinder' => $this->urlFinder, + 'entityResource' => $this->entityResource + ] + ); } /** - * @expectedException \RuntimeException + * Tests getHref with wrong id_path + * + * @expectedException RuntimeException * @expectedExceptionMessage Parameter id_path is not set. */ public function testGetHrefWithoutSetIdPath() @@ -61,7 +88,9 @@ public function testGetHrefWithoutSetIdPath() } /** - * @expectedException \RuntimeException + * Tests getHref with wrong id_path + * + * @expectedException RuntimeException * @expectedExceptionMessage Wrong id_path structure. */ public function testGetHrefIfSetWrongIdPath() @@ -70,27 +99,30 @@ public function testGetHrefIfSetWrongIdPath() $this->block->getHref(); } + /** + * Tests getHref with wrong store ID + * + * @expectedException Exception + */ public function testGetHrefWithSetStoreId() { $this->block->setData('id_path', 'type/id'); $this->block->setData('store_id', 'store_id'); - $this->storeManager->expects($this->once()) - ->method('getStore')->with('store_id') - // interrupt test execution - ->will($this->throwException(new \Exception())); - - try { - $this->block->getHref(); - } catch (\Exception $e) { - } + ->method('getStore') + ->with('store_id') + ->will($this->throwException(new Exception())); + $this->block->getHref(); } + /** + * Tests getHref with not found URL + */ public function testGetHrefIfRewriteIsNotFound() { $this->block->setData('id_path', 'entity_type/entity_id'); - $store = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getId', '__wakeUp']); + $store = $this->createPartialMock(Store::class, ['getId', '__wakeUp']); $store->expects($this->any()) ->method('getId'); @@ -105,52 +137,94 @@ public function testGetHrefIfRewriteIsNotFound() } /** - * @param string $url - * @param string $separator + * Tests getHref whether it should include the store code or not + * * @dataProvider dataProviderForTestGetHrefWithoutUrlStoreSuffix + * @param string $path + * @param bool $includeStoreCode + * @param string $expected + * @throws \ReflectionException */ - public function testGetHrefWithoutUrlStoreSuffix($url, $separator) - { - $storeId = 15; - $storeCode = 'store-code'; - $requestPath = 'request-path'; + public function testStoreCodeShouldBeIncludedInURLOnlyIfItIsConfiguredSo( + string $path, + bool $includeStoreCode, + string $expected + ) { $this->block->setData('id_path', 'entity_type/entity_id'); - - $rewrite = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); - $rewrite->expects($this->once()) - ->method('getRequestPath') - ->will($this->returnValue($requestPath)); - - $store = $this->createPartialMock( - \Magento\Store\Model\Store::class, - ['getId', 'getUrl', 'getCode', '__wakeUp'] + $objectManager = new ObjectManager($this); + + $rewrite = $this->createPartialMock(UrlRewrite::class, ['getRequestPath']); + $url = $this->createPartialMock(Url::class, ['setScope', 'getUrl']); + $urlModifier = $this->getMockForAbstractClass(ModifierInterface::class); + $config = $this->getMockForAbstractClass(ReinitableConfigInterface::class); + $store = $objectManager->getObject( + Store::class, + [ + 'storeManager' => $this->storeManager, + 'url' => $url, + 'config' => $config + ] ); - $store->expects($this->once()) - ->method('getId') - ->will($this->returnValue($storeId)); - $store->expects($this->once()) + $property = (new ReflectionClass(get_class($store)))->getProperty('urlModifier'); + $property->setAccessible(true); + $property->setValue($store, $urlModifier); + + $urlModifier->expects($this->any()) + ->method('execute') + ->willReturnArgument(0); + $config->expects($this->any()) + ->method('getValue') + ->willReturnMap( + [ + [Store::XML_PATH_USE_REWRITES, ReinitableConfigInterface::SCOPE_TYPE_DEFAULT, null, true], + [ + Store::XML_PATH_STORE_IN_URL, + ReinitableConfigInterface::SCOPE_TYPE_DEFAULT, + null, $includeStoreCode + ] + ] + ); + + $url->expects($this->any()) + ->method('setScope') + ->willReturnSelf(); + + $url->expects($this->any()) ->method('getUrl') - ->with('', ['_direct' => $requestPath]) - ->will($this->returnValue($url)); - $store->expects($this->once()) - ->method('getCode') - ->will($this->returnValue($storeCode)); + ->willReturnCallback( + function ($route, $params) use ($store) { + return rtrim($store->getBaseUrl(), '/') .'/'. ltrim($params['_direct'], '/'); + } + ); - $this->storeManager->expects($this->once()) + $store->addData(['store_id' => 1, 'code' => 'french']); + + $this->storeManager + ->expects($this->any()) ->method('getStore') - ->will($this->returnValue($store)); + ->willReturn($store); - $this->urlFinder->expects($this->once())->method('findOneByData') - ->with([ + $this->urlFinder->expects($this->once()) + ->method('findOneByData') + ->with( + [ UrlRewrite::ENTITY_ID => 'entity_id', UrlRewrite::ENTITY_TYPE => 'entity_type', - UrlRewrite::STORE_ID => $storeId, - ]) + UrlRewrite::STORE_ID => $store->getStoreId(), + ] + ) ->will($this->returnValue($rewrite)); - $this->assertEquals($url . $separator . '___store=' . $storeCode, $this->block->getHref()); + $rewrite->expects($this->once()) + ->method('getRequestPath') + ->will($this->returnValue($path)); + + $this->assertContains($expected, $this->block->getHref()); } + /** + * Tests getLabel with custom text + */ public function testGetLabelWithCustomText() { $customText = 'Some text'; @@ -158,6 +232,9 @@ public function testGetLabelWithCustomText() $this->assertEquals($customText, $this->block->getLabel()); } + /** + * Tests getLabel without custom text + */ public function testGetLabelWithoutCustomText() { $category = 'Some text'; @@ -178,17 +255,20 @@ public function testGetLabelWithoutCustomText() public function dataProviderForTestGetHrefWithoutUrlStoreSuffix() { return [ - ['url', '?'], - ['url?some_parameter', '&'], + ['/accessories.html', true, 'french/accessories.html'], + ['/accessories.html', false, '/accessories.html'], ]; } + /** + * Tests getHref with product entity and additional category id in the id_path + */ public function testGetHrefWithForProductWithCategoryIdParameter() { $storeId = 15; $this->block->setData('id_path', ProductUrlRewriteGenerator::ENTITY_TYPE . '/entity_id/category_id'); - $store = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getId', '__wakeUp']); + $store = $this->createPartialMock(Store::class, ['getId', '__wakeUp']); $store->expects($this->any()) ->method('getId') ->will($this->returnValue($storeId)); @@ -197,13 +277,16 @@ public function testGetHrefWithForProductWithCategoryIdParameter() ->method('getStore') ->will($this->returnValue($store)); - $this->urlFinder->expects($this->once())->method('findOneByData') - ->with([ - UrlRewrite::ENTITY_ID => 'entity_id', - UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, - UrlRewrite::STORE_ID => $storeId, - UrlRewrite::METADATA => ['category_id' => 'category_id'], - ]) + $this->urlFinder->expects($this->once()) + ->method('findOneByData') + ->with( + [ + UrlRewrite::ENTITY_ID => 'entity_id', + UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::STORE_ID => $storeId, + UrlRewrite::METADATA => ['category_id' => 'category_id'], + ] + ) ->will($this->returnValue(false)); $this->block->getHref(); From 528c538a79a4928f3bd2c38d0e23f2220f6dbaef Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Tue, 3 Sep 2019 09:17:57 +0300 Subject: [PATCH 592/841] magento/magento2#: Magento_Config module. Fix content in read file --- app/code/Magento/Config/README.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Config/README.md b/app/code/Magento/Config/README.md index 6214cc94b04a0..83f49a851c662 100644 --- a/app/code/Magento/Config/README.md +++ b/app/code/Magento/Config/README.md @@ -1,8 +1,6 @@ -#Config +# Magento_Config module + The Config module is designed to implement system configuration functionality. -It provides mechanisms to add, edit, store and retrieve the configuration data -for each scope (there can be a default scope as well as scopes for each website and store). +It provides mechanisms to add, edit, store and retrieve the configuration data for each scope (there can be a default scope as well as scopes for each website and store). -Modules can add items to be configured on the system configuration page by creating -system.xml files in their etc/adminhtml directories. These system.xml files get merged -to populate the forms in the config page. +Modules can add items to be configured on the system configuration page by creating system.xml files in their etc/adminhtml directories. These system.xml files get merged to populate the forms in the config page. From a8fbb7cad64ee1817b50576adcf6fc41b9ad52c9 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Mon, 2 Sep 2019 14:39:35 -0500 Subject: [PATCH 593/841] MC-19650: PayPal is not working in B2B Quote --- .../Controller/Express/AbstractExpress.php | 21 ++++++++++++++++--- .../Controller/Express/OnAuthorization.php | 1 + 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php index 7ad8fe658ec16..895cdb8d4c600 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php @@ -9,6 +9,8 @@ use Magento\Framework\App\Action\Action as AppAction; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Quote\Api\CartRepositoryInterface; use Magento\Quote\Api\Data\CartInterface; /** @@ -98,6 +100,11 @@ abstract class AbstractExpress extends AppAction implements */ protected $_customerUrl; + /** + * @var CartRepositoryInterface + */ + private $quoteRepository; + /** * @param \Magento\Framework\App\Action\Context $context * @param \Magento\Customer\Model\Session $customerSession @@ -107,6 +114,7 @@ abstract class AbstractExpress extends AppAction implements * @param \Magento\Framework\Session\Generic $paypalSession * @param \Magento\Framework\Url\Helper\Data $urlHelper * @param \Magento\Customer\Model\Url $customerUrl + * @param CartRepositoryInterface $quoteRepository */ public function __construct( \Magento\Framework\App\Action\Context $context, @@ -116,7 +124,8 @@ public function __construct( \Magento\Paypal\Model\Express\Checkout\Factory $checkoutFactory, \Magento\Framework\Session\Generic $paypalSession, \Magento\Framework\Url\Helper\Data $urlHelper, - \Magento\Customer\Model\Url $customerUrl + \Magento\Customer\Model\Url $customerUrl, + CartRepositoryInterface $quoteRepository = null ) { $this->_customerSession = $customerSession; $this->_checkoutSession = $checkoutSession; @@ -128,6 +137,7 @@ public function __construct( parent::__construct($context); $parameters = ['params' => [$this->_configMethod]]; $this->_config = $this->_objectManager->create($this->_configType, $parameters); + $this->quoteRepository = $quoteRepository ?: ObjectManager::getInstance()->get(CartRepositoryInterface::class); } /** @@ -233,7 +243,12 @@ protected function _getCheckoutSession() protected function _getQuote() { if (!$this->_quote) { - $this->_quote = $this->_getCheckoutSession()->getQuote(); + if ($this->_getSession()->getQuoteId()) { + $this->_quote = $this->quoteRepository->get($this->_getSession()->getQuoteId()); + $this->_getCheckoutSession()->replaceQuote($this->_quote); + } else { + $this->_quote = $this->_getCheckoutSession()->getQuote(); + } } return $this->_quote; } @@ -243,7 +258,7 @@ protected function _getQuote() */ public function getCustomerBeforeAuthUrl() { - return; + return null; } /** diff --git a/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php index 62f4c4c4c457a..0d7ec3fc6f32d 100644 --- a/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php +++ b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php @@ -155,6 +155,7 @@ public function execute(): ResultInterface } else { $responseContent['redirectUrl'] = $this->urlBuilder->getUrl('paypal/express/review'); $this->_checkoutSession->setQuoteId($quote->getId()); + $this->_getSession()->setQuoteId($quote->getId()); } } catch (ApiProcessableException $e) { $responseContent['success'] = false; From 48935fff1e2bff2540fba0a33f3c640f9a5554e5 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 28 Aug 2019 16:35:55 -0500 Subject: [PATCH 594/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed codestyle --- app/code/Magento/Translation/composer.json | 2 +- .../TestFramework/Dependency/PhpRule.php | 13 ++--- .../Dependency/Route/RouteMapper.php | 4 +- .../Magento/Test/Integrity/DependencyTest.php | 54 ++++++------------- 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/app/code/Magento/Translation/composer.json b/app/code/Magento/Translation/composer.json index 511238aefe7f3..e88f44e7cd039 100644 --- a/app/code/Magento/Translation/composer.json +++ b/app/code/Magento/Translation/composer.json @@ -10,7 +10,7 @@ "magento/module-backend": "*", "magento/module-developer": "*", "magento/module-store": "*", - "magento/module-theme" : "*" + "magento/module-theme": "*" }, "suggest": { "magento/module-deploy": "*" diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php index 990df68a95cf3..903b1da0ba9ad 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php @@ -285,6 +285,7 @@ private function isPluginDependency($dependent, $dependency) * @return array * @throws LocalizedException * @throws \Exception + * @SuppressWarnings(PMD.CyclomaticComplexity) */ protected function _caseGetUrl(string $currentModule, string &$contents): array { @@ -301,18 +302,18 @@ protected function _caseGetUrl(string $currentModule, string &$contents): array $routeId = $item['route_id']; $controllerName = $item['controller_name'] ?? UrlInterface::DEFAULT_CONTROLLER_NAME; $actionName = $item['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME; - if ( - in_array( - implode('/', [$routeId, $controllerName, $actionName]), - $this->getRoutesWhitelist())) { + if (in_array( + implode('/', [$routeId, $controllerName, $actionName]), + $this->getRoutesWhitelist() + )) { continue; } // skip rest - if($routeId == "rest") { //MC-17627 + if ($routeId == "rest") { //MC-17627 continue; } // skip wildcards - if($routeId == "*" || $controllerName == "*" || $actionName == "*" ) { //MC-17627 + if ($routeId == "*" || $controllerName == "*" || $actionName == "*") { //MC-17627 continue; } $modules = $this->routeMapper->getDependencyByRoutePath( diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php index a3d6fbffa8c7e..8bd6b1479768f 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php @@ -174,7 +174,7 @@ public function getDependencyByRoutePath( $dependencies = []; foreach ($this->getRouterTypes() as $routerId) { if (isset($this->getActionsMap()[$routerId][$routeId][$controllerName][$actionName])) { - $dependencies = array_merge( + $dependencies = array_merge( //phpcs:ignore $dependencies, $this->getActionsMap()[$routerId][$routeId][$controllerName][$actionName] ); @@ -243,7 +243,7 @@ private function processConfigFile(string $module, string $configFile) if (!in_array($module, $this->routers[$routerId][$routeId])) { $this->routers[$routerId][$routeId][] = $module; } - if(isset($route['frontName'])) { + if (isset($route['frontName'])) { $frontName = (string)$route['frontName']; if (!isset($this->routers[$routerId][$frontName])) { $this->routers[$routerId][$frontName] = []; diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index c890cee6f1dda..d3160be7c72c0 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -234,8 +234,7 @@ protected static function _initRules() . '/_files/dependency_test/tables_*.php'; $dbRuleTables = []; foreach (glob($replaceFilePattern) as $fileName) { - //phpcs:ignore Generic.PHP.NoSilencedErrors - $dbRuleTables = array_merge($dbRuleTables, @include $fileName); + $dbRuleTables = array_merge($dbRuleTables, @include $fileName); //phpcs:ignore } self::$_rulesInstances = [ new PhpRule( @@ -265,13 +264,15 @@ private static function getRoutesWhitelist(): array { if (is_null(self::$routesWhitelist)) { $routesWhitelistFilePattern = realpath(__DIR__) . '/_files/dependency_test/whitelist/routes_*.php'; - $routesWhitelist = []; - foreach (glob($routesWhitelistFilePattern) as $fileName) { - $routesWhitelist = array_merge($routesWhitelist, include $fileName); - } - self::$routesWhitelist = $routesWhitelist; + self::$routesWhitelist = array_merge( + ...array_map( + function ($fileName) { + return include $fileName; + }, + glob($routesWhitelistFilePattern) + ) + ); } - return self::$routesWhitelist; } @@ -295,9 +296,9 @@ protected function _getCleanedFileContents($fileType, $file) '', (string)file_get_contents($file) ); - break; case 'template': $contents = php_strip_whitespace($file); + //Removing html $contentsWithoutHtml = ''; preg_replace_callback( '~(<\?(php|=)\s+.*\?>)~sU', @@ -312,8 +313,6 @@ function ($matches) use ($contents, &$contentsWithoutHtml) { return (string)file_get_contents($file); } - - /** * @inheritdoc * @throws \Exception @@ -390,7 +389,7 @@ protected function getDependenciesFromFiles($module, $fileType, $file, $contents foreach (self::$_rulesInstances as $rule) { /** @var \Magento\TestFramework\Dependency\RuleInterface $rule */ $newDependencies = $rule->getDependencyInfo($module, $fileType, $file, $contents); - $dependencies = array_merge($dependencies, $newDependencies); + $dependencies = array_merge($dependencies, $newDependencies); //phpcs:ignore } foreach ($dependencies as $key => $dependency) { foreach (self::$whiteList as $namespace) { @@ -505,12 +504,12 @@ public function collectRedundant() foreach (array_keys(self::$mapDependencies) as $module) { $declared = $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_DECLARED); + //phpcs:ignore $found = array_merge( $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_FOUND), $this->_getDependencies($module, self::TYPE_SOFT, self::MAP_TYPE_FOUND), $schemaDependencyProvider->getDeclaredExistingModuleDependencies($module) ); - $found['Magento\Framework'] = 'Magento\Framework'; $this->_setDependencies($module, self::TYPE_HARD, self::MAP_TYPE_REDUNDANT, array_diff($declared, $found)); } @@ -574,37 +573,16 @@ protected function _prepareFiles($fileType, $files, $skip = null) */ public function getAllFiles() { - $files = []; - - // Get all php files - $files = array_merge( - $files, + return array_merge( $this->_prepareFiles( 'php', Files::init()->getPhpFiles(Files::INCLUDE_APP_CODE | Files::AS_DATA_SET | Files::INCLUDE_NON_CLASSES), true - ) - ); - - // Get all configuration files - $files = array_merge( - $files, - $this->_prepareFiles('config', Files::init()->getConfigFiles()) - ); - - //Get all layout updates files - $files = array_merge( - $files, - $this->_prepareFiles('layout', Files::init()->getLayoutFiles()) - ); - - // Get all template files - $files = array_merge( - $files, + ), + $this->_prepareFiles('config', Files::init()->getConfigFiles()), + $this->_prepareFiles('layout', Files::init()->getLayoutFiles()), $this->_prepareFiles('template', Files::init()->getPhtmlFiles()) ); - - return $files; } /** From bfa94929a9ac32d35297307498746baa679d002d Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Tue, 3 Sep 2019 11:17:24 -0500 Subject: [PATCH 595/841] MC-19702: Add input_type to customAttributeMetadata query - Fix attribute_options ouput --- .../Model/Resolver/AttributeOptions.php | 24 ++--- .../DataProvider/AttributeOptions.php | 6 +- .../Catalog/ProductAttributeOptionsTest.php | 93 +++++++++++++++++++ 3 files changed, 110 insertions(+), 13 deletions(-) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php b/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php index e4c27adc60247..7361d52372cd6 100644 --- a/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php +++ b/app/code/Magento/EavGraphQl/Model/Resolver/AttributeOptions.php @@ -57,29 +57,31 @@ public function resolve( array $args = null ) : Value { - return $this->valueFactory->create(function () use ($value) { - $entityType = $this->getEntityType($value); - $attributeCode = $this->getAttributeCode($value); + return $this->valueFactory->create( + function () use ($value) { + $entityType = $this->getEntityType($value); + $attributeCode = $this->getAttributeCode($value); - $optionsData = $this->getAttributeOptionsData($entityType, $attributeCode); - return $optionsData; - }); + $optionsData = $this->getAttributeOptionsData($entityType, $attributeCode); + return $optionsData; + } + ); } /** * Get entity type * * @param array $value - * @return int + * @return string * @throws LocalizedException */ - private function getEntityType(array $value): int + private function getEntityType(array $value): string { if (!isset($value['entity_type'])) { throw new LocalizedException(__('"Entity type should be specified')); } - return (int)$value['entity_type']; + return $value['entity_type']; } /** @@ -101,13 +103,13 @@ private function getAttributeCode(array $value): string /** * Get attribute options data * - * @param int $entityType + * @param string $entityType * @param string $attributeCode * @return array * @throws GraphQlInputException * @throws GraphQlNoSuchEntityException */ - private function getAttributeOptionsData(int $entityType, string $attributeCode): array + private function getAttributeOptionsData(string $entityType, string $attributeCode): array { try { $optionsData = $this->attributeOptionsDataProvider->getData($entityType, $attributeCode); diff --git a/app/code/Magento/EavGraphQl/Model/Resolver/DataProvider/AttributeOptions.php b/app/code/Magento/EavGraphQl/Model/Resolver/DataProvider/AttributeOptions.php index 900a31c1093ed..3371fbe658c9c 100644 --- a/app/code/Magento/EavGraphQl/Model/Resolver/DataProvider/AttributeOptions.php +++ b/app/code/Magento/EavGraphQl/Model/Resolver/DataProvider/AttributeOptions.php @@ -29,11 +29,13 @@ public function __construct( } /** - * @param int $entityType + * Get attribute options data + * + * @param string $entityType * @param string $attributeCode * @return array */ - public function getData(int $entityType, string $attributeCode): array + public function getData(string $entityType, string $attributeCode): array { $options = $this->optionManager->getItems($entityType, $attributeCode); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php new file mode 100644 index 0000000000000..53e09bf590e3c --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Catalog; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class ProductAttributeOptionsTest extends GraphQlAbstract +{ + /** + * Test that custom attribute options are returned correctly + * + * @magentoApiDataFixture Magento/Catalog/_files/dropdown_attribute.php + */ + public function testCustomAttributeMetadataOptions() + { + $query + = <<<QUERY +{ + customAttributeMetadata(attributes: + [ + { + attribute_code:"description", + entity_type:"catalog_product" + }, + { + attribute_code:"status", + entity_type:"catalog_product" + }, + { + attribute_code:"dropdown_attribute", + entity_type:"catalog_product" + } + ] + ) + { + items + { + attribute_code + attribute_type + entity_type + input_type + attribute_options{ + label + value + } + } + } + } +QUERY; + $response = $this->graphQlQuery($query); + + $expectedOptionArray = [ + [], // description attribute has no options + [ + [ + 'label' => 'Enabled', + 'value' => '1' + ], + [ + 'label' => 'Disabled', + 'value' => '2' + ] + ], + [ + [ + 'label' => 'Option 1', + 'value' => '10' + ], + [ + 'label' => 'Option 2', + 'value' => '11' + ], + [ + 'label' => 'Option 3', + 'value' => '12' + ] + ] + ]; + + $this->assertNotEmpty($response['customAttributeMetadata']['items']); + $actualAttributes = $response['customAttributeMetadata']['items']; + + foreach ($expectedOptionArray as $index => $expectedOptions) { + $actualOption = $actualAttributes[$index]['attribute_options']; + $this->assertEquals($expectedOptions, $actualOption); + } + } +} From e115b067996b98e1412afe7129aa9933bda88711 Mon Sep 17 00:00:00 2001 From: konarshankar07 <konar.shankar2013@gmail.com> Date: Wed, 4 Sep 2019 00:08:28 +0530 Subject: [PATCH 596/841] Review rating block cleared on reset button click --- app/code/Magento/Review/Block/Adminhtml/Add.php | 4 +++- app/code/Magento/Review/view/adminhtml/web/js/rating.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Review/Block/Adminhtml/Add.php b/app/code/Magento/Review/Block/Adminhtml/Add.php index 14392220a3db4..bfa75b559a7ff 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Add.php +++ b/app/code/Magento/Review/Block/Adminhtml/Add.php @@ -37,10 +37,11 @@ protected function _construct() '; // @codingStandardsIgnoreStart $this->_formInitScripts[] = ' - require(["jquery","prototype"], function(jQuery){ + require(["jquery","Magento_Review/js/rating","prototype"], function(jQuery, rating){ window.review = function() { return { reviewFormEditSelector: "#edit_form", + ratingSelector: "[data-widget=ratingControl]", productInfoUrl : null, formHidden : true, gridRowClick : function(data, click) { @@ -71,6 +72,7 @@ protected function _construct() }, formReset: function() { jQuery(review.reviewFormEditSelector).trigger(\'reset\'); + jQuery(review.ratingSelector).ratingControl(\'removeRating\'); }, updateRating: function() { elements = [$("select_stores"), $("rating_detail").getElementsBySelector("input[type=\'radio\']")].flatten(); diff --git a/app/code/Magento/Review/view/adminhtml/web/js/rating.js b/app/code/Magento/Review/view/adminhtml/web/js/rating.js index b8d1b1b241b8f..66067c6b50dcb 100644 --- a/app/code/Magento/Review/view/adminhtml/web/js/rating.js +++ b/app/code/Magento/Review/view/adminhtml/web/js/rating.js @@ -62,6 +62,14 @@ define([ checkedInputs.nextAll('label').addBack().css('color', this.options.colorFilled).data('checked', true); checkedInputs.prevAll('label').css('color', this.options.colorUnfilled).data('checked', false); + }, + + /** + * Remove rating when form reset + */ + removeRating: function () { + var checkedInputs = this.element.find('input[type="radio"]'); + checkedInputs.nextAll('label').css('color', this.options.colorUnfilled).data('checked', false); } }); From c1e363e26590b32d99330d1ce47496c35bdb8864 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Tue, 3 Sep 2019 15:34:56 -0500 Subject: [PATCH 597/841] MC-19706: URL rewrite not created upon creating the product --- app/code/Magento/Catalog/Model/Product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index fc9fffb2a7e9a..51822523957bf 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -832,7 +832,7 @@ public function getStoreIds() if (!$this->hasStoreIds()) { $storeIds = []; if ($websiteIds = $this->getWebsiteIds()) { - if ($this->_storeManager->isSingleStoreMode()) { + if ($this->_storeManager->isSingleStoreMode() && !$this->isObjectNew()) { $websiteIds = array_keys($websiteIds); } foreach ($websiteIds as $websiteId) { From 0a67f76301153745e1d3751444223916e9d7b927 Mon Sep 17 00:00:00 2001 From: Gustavo Dauer <gustavo.dauer@hotmail.com> Date: Tue, 3 Sep 2019 18:17:46 -0300 Subject: [PATCH 598/841] class refactoring --- .../Controller/Cart/UpdateItemQty.php | 118 ++++++++++++++---- 1 file changed, 92 insertions(+), 26 deletions(-) diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php index 77adbb4a2b67c..66882fc91aaee 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php @@ -8,15 +8,23 @@ namespace Magento\Checkout\Controller\Cart; use Magento\Checkout\Model\Cart\RequestQuantityProcessor; +use Magento\Checkout\Model\Session as CheckoutSession; use Magento\Framework\App\Action\Context; +use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; use Magento\Framework\Exception\LocalizedException; -use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; use Magento\Quote\Model\Quote\Item; +use Magento\Framework\App\Action\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; use Psr\Log\LoggerInterface; -class UpdateItemQty extends \Magento\Framework\App\Action\Action +/** + * Class UpdateItemQty + * + * @package Magento\Checkout\Controller\Cart + */ +class UpdateItemQty extends Action implements HttpPostActionInterface { /** * @var RequestQuantityProcessor @@ -44,13 +52,16 @@ class UpdateItemQty extends \Magento\Framework\App\Action\Action private $logger; /** - * @param Context $context, - * @param RequestQuantityProcessor $quantityProcessor - * @param FormKeyValidator $formKeyValidator - * @param CheckoutSession $checkoutSession - * @param Json $json - * @param LoggerInterface $logger + * UpdateItemQty constructor + * + * @param Context $context Parent dependency + * @param RequestQuantityProcessor $quantityProcessor Request quantity + * @param FormKeyValidator $formKeyValidator Form validator + * @param CheckoutSession $checkoutSession Session + * @param Json $json Json serializer + * @param LoggerInterface $logger Logger */ + public function __construct( Context $context, RequestQuantityProcessor $quantityProcessor, @@ -60,31 +71,29 @@ public function __construct( LoggerInterface $logger ) { $this->quantityProcessor = $quantityProcessor; - $this->formKeyValidator = $formKeyValidator; - $this->checkoutSession = $checkoutSession; - $this->json = $json; - $this->logger = $logger; + $this->formKeyValidator = $formKeyValidator; + $this->checkoutSession = $checkoutSession; + $this->json = $json; + $this->logger = $logger; parent::__construct($context); + } + /** + * Controller execute method + * * @return void */ public function execute() { try { - if (!$this->formKeyValidator->validate($this->getRequest())) { - throw new LocalizedException( - __('Something went wrong while saving the page. Please refresh the page and try again.') - ); - } + $this->validateRequest(); + $this->validateFormKey(); $cartData = $this->getRequest()->getParam('cart'); - if (!is_array($cartData)) { - throw new LocalizedException( - __('Something went wrong while saving the page. Please refresh the page and try again.') - ); - } + + $this->validateCartData($cartData); $cartData = $this->quantityProcessor->process($cartData); $quote = $this->checkoutSession->getQuote(); @@ -109,15 +118,17 @@ public function execute() /** * Updates quote item quantity. * - * @param Item $item + * @param Item $item * @param float $qty + * * @throws LocalizedException + * + * @return void */ private function updateItemQuantity(Item $item, float $qty) { - $item->clearMessage(); - if ($qty > 0) { + $item->clearMessage(); $item->setQty($qty); if ($item->getHasError()) { @@ -130,6 +141,7 @@ private function updateItemQuantity(Item $item, float $qty) * JSON response builder. * * @param string $error + * * @return void */ private function jsonResponse(string $error = '') @@ -143,6 +155,7 @@ private function jsonResponse(string $error = '') * Returns response data. * * @param string $error + * * @return array */ private function getResponseData(string $error = ''): array @@ -160,4 +173,57 @@ private function getResponseData(string $error = ''): array return $response; } + + /** + * Validates the Request HTTP method + * + * @throws NotFoundException + * + * @return void + */ + private function validateRequest() + { + if ($this->getRequest()->isPost() === false) { + throw new NotFoundException( + __('Page Not Found') + ); + } + + } + + /** + * Validates form key + * + * @throws LocalizedException + * + * @return void + */ + private function validateFormKey() + { + if (!$this->formKeyValidator->validate($this->getRequest())) { + throw new LocalizedException( + __('Something went wrong while saving the page. Please refresh the page and try again.') + ); + } + + } + + /** + * Validates cart data + * + * @param array|null $cartData + * + * @throws LocalizedException + * + * @return void + */ + private function validateCartData($cartData = null) + { + if (!is_array($cartData)) { + throw new LocalizedException( + __('Something went wrong while saving the page. Please refresh the page and try again.') + ); + } + + } } From d2a09d3c63eabcbbec6eadbbfd9896b55e000b31 Mon Sep 17 00:00:00 2001 From: Gustavo Dauer <gustavo.dauer@hotmail.com> Date: Tue, 3 Sep 2019 18:46:52 -0300 Subject: [PATCH 599/841] testCodeStyle --- .../Controller/Cart/UpdateItemQty.php | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php index 66882fc91aaee..c731df15ba7d1 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php @@ -9,14 +9,14 @@ use Magento\Checkout\Model\Cart\RequestQuantityProcessor; use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Serialize\Serializer\Json; use Magento\Quote\Model\Quote\Item; -use Magento\Framework\App\Action\Action; -use Magento\Framework\App\Action\HttpPostActionInterface; use Psr\Log\LoggerInterface; /** @@ -26,6 +26,7 @@ */ class UpdateItemQty extends Action implements HttpPostActionInterface { + /** * @var RequestQuantityProcessor */ @@ -76,9 +77,7 @@ public function __construct( $this->json = $json; $this->logger = $logger; parent::__construct($context); - - } - + }//end __construct() /** * Controller execute method @@ -96,11 +95,11 @@ public function execute() $this->validateCartData($cartData); $cartData = $this->quantityProcessor->process($cartData); - $quote = $this->checkoutSession->getQuote(); + $quote = $this->checkoutSession->getQuote(); foreach ($cartData as $itemId => $itemInfo) { $item = $quote->getItemById($itemId); - $qty = isset($itemInfo['qty']) ? (double)$itemInfo['qty'] : 0; + $qty = isset($itemInfo['qty']) ? (double) $itemInfo['qty'] : 0; if ($item) { $this->updateItemQuantity($item, $qty); } @@ -112,8 +111,8 @@ public function execute() } catch (\Exception $e) { $this->logger->critical($e->getMessage()); $this->jsonResponse('Something went wrong while saving the page. Please refresh the page and try again.'); - } - } + }//end try + }//end execute() /** * Updates quote item quantity. @@ -135,7 +134,7 @@ private function updateItemQuantity(Item $item, float $qty) throw new LocalizedException(__($item->getMessage())); } } - } + }//end updateItemQuantity() /** * JSON response builder. @@ -149,7 +148,7 @@ private function jsonResponse(string $error = '') $this->getResponse()->representJson( $this->json->serialize($this->getResponseData($error)) ); - } + }//end jsonResponse() /** * Returns response data. @@ -160,19 +159,17 @@ private function jsonResponse(string $error = '') */ private function getResponseData(string $error = ''): array { - $response = [ - 'success' => true, - ]; + $response = ['success' => true]; if (!empty($error)) { $response = [ - 'success' => false, + 'success' => false, 'error_message' => $error, ]; } return $response; - } + }//end getResponseData() /** * Validates the Request HTTP method @@ -184,12 +181,9 @@ private function getResponseData(string $error = ''): array private function validateRequest() { if ($this->getRequest()->isPost() === false) { - throw new NotFoundException( - __('Page Not Found') - ); + throw new NotFoundException(__('Page Not Found')); } - - } + }//end validateRequest() /** * Validates form key @@ -205,8 +199,7 @@ private function validateFormKey() __('Something went wrong while saving the page. Please refresh the page and try again.') ); } - - } + }//end validateFormKey() /** * Validates cart data @@ -224,6 +217,5 @@ private function validateCartData($cartData = null) __('Something went wrong while saving the page. Please refresh the page and try again.') ); } - - } -} + }//end validateCartData() +}//end class From 720799bb31947df6e8ca4fa64275962380e99422 Mon Sep 17 00:00:00 2001 From: Gustavo Dauer <gustavo.dauer@hotmail.com> Date: Tue, 3 Sep 2019 19:20:40 -0300 Subject: [PATCH 600/841] updated PHPDoc --- .../Controller/Cart/UpdateItemQty.php | 64 ++++++++----------- 1 file changed, 28 insertions(+), 36 deletions(-) diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php index c731df15ba7d1..508005cf392ed 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php @@ -20,7 +20,7 @@ use Psr\Log\LoggerInterface; /** - * Class UpdateItemQty + * UpdateItemQty ajax request * * @package Magento\Checkout\Controller\Cart */ @@ -55,12 +55,12 @@ class UpdateItemQty extends Action implements HttpPostActionInterface /** * UpdateItemQty constructor * - * @param Context $context Parent dependency + * @param Context $context * @param RequestQuantityProcessor $quantityProcessor Request quantity - * @param FormKeyValidator $formKeyValidator Form validator - * @param CheckoutSession $checkoutSession Session - * @param Json $json Json serializer - * @param LoggerInterface $logger Logger + * @param FormKeyValidator $formKeyValidator Form validator + * @param CheckoutSession $checkoutSession Session + * @param Json $json Json serializer + * @param LoggerInterface $logger Logger */ public function __construct( @@ -72,12 +72,12 @@ public function __construct( LoggerInterface $logger ) { $this->quantityProcessor = $quantityProcessor; - $this->formKeyValidator = $formKeyValidator; - $this->checkoutSession = $checkoutSession; - $this->json = $json; - $this->logger = $logger; + $this->formKeyValidator = $formKeyValidator; + $this->checkoutSession = $checkoutSession; + $this->json = $json; + $this->logger = $logger; parent::__construct($context); - }//end __construct() + } /** * Controller execute method @@ -95,11 +95,11 @@ public function execute() $this->validateCartData($cartData); $cartData = $this->quantityProcessor->process($cartData); - $quote = $this->checkoutSession->getQuote(); + $quote = $this->checkoutSession->getQuote(); foreach ($cartData as $itemId => $itemInfo) { $item = $quote->getItemById($itemId); - $qty = isset($itemInfo['qty']) ? (double) $itemInfo['qty'] : 0; + $qty = isset($itemInfo['qty']) ? (double) $itemInfo['qty'] : 0; if ($item) { $this->updateItemQuantity($item, $qty); } @@ -111,18 +111,16 @@ public function execute() } catch (\Exception $e) { $this->logger->critical($e->getMessage()); $this->jsonResponse('Something went wrong while saving the page. Please refresh the page and try again.'); - }//end try - }//end execute() + } + } /** * Updates quote item quantity. * - * @param Item $item + * @param Item $item * @param float $qty - * - * @throws LocalizedException - * * @return void + * @throws LocalizedException */ private function updateItemQuantity(Item $item, float $qty) { @@ -134,13 +132,12 @@ private function updateItemQuantity(Item $item, float $qty) throw new LocalizedException(__($item->getMessage())); } } - }//end updateItemQuantity() + } /** * JSON response builder. * * @param string $error - * * @return void */ private function jsonResponse(string $error = '') @@ -148,13 +145,12 @@ private function jsonResponse(string $error = '') $this->getResponse()->representJson( $this->json->serialize($this->getResponseData($error)) ); - }//end jsonResponse() + } /** * Returns response data. * * @param string $error - * * @return array */ private function getResponseData(string $error = ''): array @@ -163,34 +159,32 @@ private function getResponseData(string $error = ''): array if (!empty($error)) { $response = [ - 'success' => false, + 'success' => false, 'error_message' => $error, ]; } return $response; - }//end getResponseData() + } /** * Validates the Request HTTP method * - * @throws NotFoundException - * * @return void + * @throws NotFoundException */ private function validateRequest() { if ($this->getRequest()->isPost() === false) { throw new NotFoundException(__('Page Not Found')); } - }//end validateRequest() + } /** * Validates form key * - * @throws LocalizedException - * * @return void + * @throws LocalizedException */ private function validateFormKey() { @@ -199,16 +193,14 @@ private function validateFormKey() __('Something went wrong while saving the page. Please refresh the page and try again.') ); } - }//end validateFormKey() + } /** * Validates cart data * * @param array|null $cartData - * - * @throws LocalizedException - * * @return void + * @throws LocalizedException */ private function validateCartData($cartData = null) { @@ -217,5 +209,5 @@ private function validateCartData($cartData = null) __('Something went wrong while saving the page. Please refresh the page and try again.') ); } - }//end validateCartData() -}//end class + } +} From 57a35468c9be951a52bd86f0363aa042e6415052 Mon Sep 17 00:00:00 2001 From: Gustavo Dauer <gustavo.dauer@hotmail.com> Date: Tue, 3 Sep 2019 19:22:34 -0300 Subject: [PATCH 601/841] updated PHPDoc construct --- .../Magento/Checkout/Controller/Cart/UpdateItemQty.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php index 508005cf392ed..9d17e32b2c93d 100644 --- a/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php +++ b/app/code/Magento/Checkout/Controller/Cart/UpdateItemQty.php @@ -56,11 +56,11 @@ class UpdateItemQty extends Action implements HttpPostActionInterface * UpdateItemQty constructor * * @param Context $context - * @param RequestQuantityProcessor $quantityProcessor Request quantity - * @param FormKeyValidator $formKeyValidator Form validator - * @param CheckoutSession $checkoutSession Session - * @param Json $json Json serializer - * @param LoggerInterface $logger Logger + * @param RequestQuantityProcessor $quantityProcessor + * @param FormKeyValidator $formKeyValidator + * @param CheckoutSession $checkoutSession + * @param Json $json + * @param LoggerInterface $logger */ public function __construct( From 19515786a9296e697c789c0a06df8352dd79d12a Mon Sep 17 00:00:00 2001 From: Arushi Bansal <53007383+arushibansal013@users.noreply.github.com> Date: Wed, 4 Sep 2019 10:54:08 +0530 Subject: [PATCH 602/841] Reverting inherit doc as suggested by reviewer --- .../DataProvider/Product/Form/Modifier/BundlePrice.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php index 20f0258cb7a36..2c64ef69604a2 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php @@ -41,10 +41,7 @@ public function __construct( } /** - * Modified modify meta for bundle product - * - * @param array $meta - * @return array + * @inheritdoc */ public function modifyMeta(array $meta) { @@ -97,10 +94,7 @@ public function modifyMeta(array $meta) } /** - * Modify data for bundle product - * - * @param array $data - * @return array + * @inheritdoc */ public function modifyData(array $data) { From 205f7e6c9413955fa6c357ffb82b35564d5d519e Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 4 Sep 2019 10:18:15 +0300 Subject: [PATCH 603/841] Adding the integration callback url validation --- .../Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php b/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php index d8684e635b943..4042c2ebde87d 100644 --- a/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php +++ b/app/code/Magento/Integration/Block/Adminhtml/Integration/Edit/Tab/Info.php @@ -146,6 +146,7 @@ protected function _addGeneralFieldset($form, $integrationData) 'label' => __('Callback URL'), 'name' => self::DATA_ENDPOINT, 'disabled' => $disabled, + 'class' => 'validate-url', // @codingStandardsIgnoreStart 'note' => __( 'Enter URL where Oauth credentials can be sent when using Oauth for token exchange. We strongly recommend using https://.' From ff0e6c0bbf2a48c782460f3a73fc16c702dcdf2e Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Wed, 4 Sep 2019 10:51:16 +0300 Subject: [PATCH 604/841] graphQl-889: base_amount marked as deprecated --- .../ShippingAddress/AvailableShippingMethods.php | 7 ++----- .../ShippingAddress/SelectedShippingMethod.php | 7 +++---- app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 4 ++-- .../Customer/GetAvailableShippingMethodsTest.php | 8 -------- .../Customer/GetSelectedShippingMethodTest.php | 13 ------------- .../Quote/Guest/GetAvailableShippingMethodsTest.php | 8 -------- .../Quote/Guest/GetSelectedShippingMethodTest.php | 13 ------------- 7 files changed, 7 insertions(+), 53 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php index e5dd1d73b80b5..53c78de98e2e6 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php @@ -98,11 +98,8 @@ private function processMoneyTypeData(array $data, string $quoteCurrencyCode, St $data['amount'] = ['value' => $data['amount'], 'currency' => $quoteCurrencyCode]; } - if (isset($data['base_amount'])) { - /** @var Currency $currency */ - $currency = $store->getBaseCurrency(); - $data['base_amount'] = ['value' => $data['base_amount'], 'currency' => $currency->getCode()]; - } + /** @deprecated The field should not be used on the storefront */ + $data['base_amount'] = null; if (isset($data['price_excl_tax'])) { $data['price_excl_tax'] = ['value' => $data['price_excl_tax'], 'currency' => $quoteCurrencyCode]; diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php index f2dacf6d007f3..c4969eb53840b 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php @@ -58,10 +58,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value 'value' => $address->getShippingAmount(), 'currency' => $address->getQuote()->getQuoteCurrencyCode(), ], - 'base_amount' => [ - 'value' => $address->getBaseShippingAmount(), - 'currency' => $currency->getCode(), - ], + /** @deprecated The field should not be used on the storefront */ + 'base_amount' => null, ]; } else { $data = [ @@ -70,6 +68,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value 'carrier_title' => $carrierTitle, 'method_title' => $methodTitle, 'amount' => null, + /** @deprecated The field should not be used on the storefront */ 'base_amount' => null, ]; } diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index c458b5e9dc05d..08bb78ba776c4 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -244,7 +244,7 @@ type SelectedShippingMethod { carrier_title: String method_title: String amount: Money - base_amount: Money + base_amount: Money @deprecated(reason: "The field should not be used on the storefront") } type AvailableShippingMethod { @@ -254,7 +254,7 @@ type AvailableShippingMethod { method_title: String @doc(description: "Could be null if method is not available") error_message: String amount: Money! - base_amount: Money @doc(description: "Could be null if method is not available") + base_amount: Money @deprecated(reason: "The field should not be used on the storefront") price_excl_tax: Money! price_incl_tax: Money! available: Boolean! diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php index 02c22a0e902be..f950d35f54658 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailableShippingMethodsTest.php @@ -62,10 +62,6 @@ public function testGetAvailableShippingMethods() 'value' => 10, 'currency' => 'USD', ], - 'base_amount' => [ - 'value' => 10, - 'currency' => 'USD', - ], 'carrier_code' => 'flatrate', 'carrier_title' => 'Flat Rate', 'error_message' => '', @@ -176,10 +172,6 @@ private function getQuery(string $maskedQuoteId): string value currency } - base_amount { - value - currency - } carrier_code carrier_title error_message diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php index 9bb36bf8f0929..1fafc753e498e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php @@ -78,14 +78,6 @@ public function testGetSelectedShippingMethod() self::assertEquals(10, $amount['value']); self::assertArrayHasKey('currency', $amount); self::assertEquals('USD', $amount['currency']); - - self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); - $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; - - self::assertArrayHasKey('value', $baseAmount); - self::assertEquals(10, $baseAmount['value']); - self::assertArrayHasKey('currency', $baseAmount); - self::assertEquals('USD', $baseAmount['currency']); } /** @@ -188,7 +180,6 @@ public function testGetGetSelectedShippingMethodIfShippingMethodIsNotSet() self::assertNull($shippingAddress['selected_shipping_method']['carrier_title']); self::assertNull($shippingAddress['selected_shipping_method']['method_title']); self::assertNull($shippingAddress['selected_shipping_method']['amount']); - self::assertNull($shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -240,10 +231,6 @@ private function getQuery(string $maskedQuoteId): string value currency } - base_amount { - value - currency - } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php index 5d90d26d4983c..81222a84435c8 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailableShippingMethodsTest.php @@ -54,10 +54,6 @@ public function testGetAvailableShippingMethods() 'value' => 10, 'currency' => 'USD', ], - 'base_amount' => [ - 'value' => 10, - 'currency' => 'USD', - ], 'carrier_code' => 'flatrate', 'carrier_title' => 'Flat Rate', 'error_message' => '', @@ -144,10 +140,6 @@ private function getQuery(string $maskedQuoteId): string value currency } - base_amount { - value - currency - } carrier_code carrier_title error_message diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php index 5d1033b39819e..fa22786e7acaa 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php @@ -70,14 +70,6 @@ public function testGetSelectedShippingMethod() self::assertEquals(10, $amount['value']); self::assertArrayHasKey('currency', $amount); self::assertEquals('USD', $amount['currency']); - - self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); - $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; - - self::assertArrayHasKey('value', $baseAmount); - self::assertEquals(10, $baseAmount['value']); - self::assertArrayHasKey('currency', $baseAmount); - self::assertEquals('USD', $baseAmount['currency']); } /** @@ -158,7 +150,6 @@ public function testGetGetSelectedShippingMethodIfShippingMethodIsNotSet() self::assertNull($shippingAddress['selected_shipping_method']['carrier_title']); self::assertNull($shippingAddress['selected_shipping_method']['method_title']); self::assertNull($shippingAddress['selected_shipping_method']['amount']); - self::assertNull($shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -195,10 +186,6 @@ private function getQuery(string $maskedQuoteId): string value currency } - base_amount { - value - currency - } } } } From 86df1094d37edf114b3142fc688458b99d592a7b Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Wed, 4 Sep 2019 10:56:27 +0300 Subject: [PATCH 605/841] magento/magento2#23021: Static test fix. --- .../ConfigurableProduct/Model/Product/VariationHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index ccd5360d0b6ee..df2a9707f18d5 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -106,6 +106,7 @@ public function generateSimpleProducts($parentProduct, $productsData) $this->fillSimpleProductData( $newSimpleProduct, $parentProduct, + // phpcs:ignore Magento2.Performance.ForeachArrayMerge array_merge($simpleProductData, $configurableAttribute) ); $newSimpleProduct->save(); From c413b69322f3793313e899df210219a474f0d284 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 4 Sep 2019 11:00:53 +0300 Subject: [PATCH 606/841] Fixing the encryption key switching field --- .../Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php b/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php index aae6fbfe1ce35..56c647057189f 100644 --- a/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php +++ b/app/code/Magento/EncryptionKey/Block/Adminhtml/Crypt/Key/Form.php @@ -45,7 +45,7 @@ protected function _prepareForm() 'name' => 'generate_random', 'label' => __('Auto-generate a Key'), 'options' => [0 => __('No'), 1 => __('Yes')], - 'onclick' => "var cryptKey = jQuery('#crypt_key'); var cryptKeyBlock = cryptKey.parent().parent(); ". + 'onchange' => "var cryptKey = jQuery('#crypt_key'); var cryptKeyBlock = cryptKey.parent().parent(); ". "cryptKey.prop('disabled', this.value === '1'); " . "if (cryptKey.prop('disabled')) { cryptKeyBlock.hide() } " . "else { cryptKeyBlock.show() }", From 4013a390348bf15ae48ddcaeecadae402351cf33 Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Wed, 4 Sep 2019 11:49:01 +0300 Subject: [PATCH 607/841] graphQl-889: fixed tests --- .../ShippingAddress/AvailableShippingMethods.php | 6 ++---- .../ShippingAddress/SelectedShippingMethod.php | 3 --- .../SetOfflineShippingMethodsOnCartTest.php | 14 +------------- .../Customer/SetShippingMethodsOnCartTest.php | 12 ------------ .../Guest/SetOfflineShippingMethodsOnCartTest.php | 14 +------------- .../Quote/Guest/SetShippingMethodsOnCartTest.php | 12 ------------ 6 files changed, 4 insertions(+), 57 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php index 53c78de98e2e6..eebed5aab6cc9 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php @@ -75,8 +75,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value ); $methods[] = $this->processMoneyTypeData( $methodData, - $cart->getQuoteCurrencyCode(), - $context->getExtensionAttributes()->getStore() + $cart->getQuoteCurrencyCode() ); } } @@ -88,11 +87,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value * * @param array $data * @param string $quoteCurrencyCode - * @param StoreInterface $store * @return array * @throws NoSuchEntityException */ - private function processMoneyTypeData(array $data, string $quoteCurrencyCode, StoreInterface $store): array + private function processMoneyTypeData(array $data, string $quoteCurrencyCode): array { if (isset($data['amount'])) { $data['amount'] = ['value' => $data['amount'], 'currency' => $quoteCurrencyCode]; diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php index c4969eb53840b..fb51d17b042df 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php @@ -46,9 +46,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value } } - /** @var Currency $currency */ - $currency = $context->getExtensionAttributes()->getStore()->getBaseCurrency(); - $data = [ 'carrier_code' => $carrierCode, 'method_code' => $methodCode, diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php index dcb5539fb3d49..fa8a7da092f5e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetOfflineShippingMethodsOnCartTest.php @@ -53,7 +53,6 @@ protected function setUp() * @param string $carrierTitle * @param string $methodTitle * @param array $amount - * @param array $baseAmount * @throws \Magento\Framework\Exception\NoSuchEntityException * @dataProvider offlineShippingMethodDataProvider */ @@ -62,8 +61,7 @@ public function testSetOfflineShippingMethod( string $methodCode, string $carrierTitle, string $methodTitle, - array $amount, - array $baseAmount + array $amount ) { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -96,9 +94,6 @@ public function testSetOfflineShippingMethod( self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); self::assertEquals($amount, $shippingAddress['selected_shipping_method']['amount']); - - self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); - self::assertEquals($baseAmount, $shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -113,7 +108,6 @@ public function offlineShippingMethodDataProvider(): array 'Flat Rate', 'Fixed', ['value' => 10, 'currency' => 'USD'], - ['value' => 10, 'currency' => 'USD'], ], 'tablerate_bestway' => [ 'tablerate', @@ -121,7 +115,6 @@ public function offlineShippingMethodDataProvider(): array 'Best Way', 'Table Rate', ['value' => 10, 'currency' => 'USD'], - ['value' => 10, 'currency' => 'USD'], ], 'freeshipping_freeshipping' => [ 'freeshipping', @@ -129,7 +122,6 @@ public function offlineShippingMethodDataProvider(): array 'Free Shipping', 'Free', ['value' => 0, 'currency' => 'USD'], - ['value' => 0, 'currency' => 'USD'], ], ]; } @@ -166,10 +158,6 @@ private function getQuery( value currency } - base_amount { - value - currency - } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php index 8197db7e7fef1..278f21f30b72d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php @@ -85,14 +85,6 @@ public function testSetShippingMethodOnCartWithSimpleProduct() self::assertEquals(10, $amount['value']); self::assertArrayHasKey('currency', $amount); self::assertEquals('USD', $amount['currency']); - - self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); - $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; - - self::assertArrayHasKey('value', $baseAmount); - self::assertEquals(10, $baseAmount['value']); - self::assertArrayHasKey('currency', $baseAmount); - self::assertEquals('USD', $baseAmount['currency']); } /** @@ -379,10 +371,6 @@ private function getQuery( value currency } - base_amount { - value - currency - } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php index bf3abe557ef82..921335e9e2082 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetOfflineShippingMethodsOnCartTest.php @@ -45,7 +45,6 @@ protected function setUp() * @param string $carrierTitle * @param string $methodTitle * @param array $amount - * @param array $baseAmount * @throws \Magento\Framework\Exception\NoSuchEntityException * @dataProvider offlineShippingMethodDataProvider */ @@ -54,8 +53,7 @@ public function testSetOfflineShippingMethod( string $methodCode, string $carrierTitle, string $methodTitle, - array $amount, - array $baseAmount + array $amount ) { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -88,9 +86,6 @@ public function testSetOfflineShippingMethod( self::assertArrayHasKey('amount', $shippingAddress['selected_shipping_method']); self::assertEquals($amount, $shippingAddress['selected_shipping_method']['amount']); - - self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); - self::assertEquals($baseAmount, $shippingAddress['selected_shipping_method']['base_amount']); } /** @@ -105,7 +100,6 @@ public function offlineShippingMethodDataProvider(): array 'Flat Rate', 'Fixed', ['value' => 10, 'currency' => 'USD'], - ['value' => 10, 'currency' => 'USD'], ], 'tablerate_bestway' => [ 'tablerate', @@ -113,7 +107,6 @@ public function offlineShippingMethodDataProvider(): array 'Best Way', 'Table Rate', ['value' => 10, 'currency' => 'USD'], - ['value' => 10, 'currency' => 'USD'], ], 'freeshipping_freeshipping' => [ 'freeshipping', @@ -121,7 +114,6 @@ public function offlineShippingMethodDataProvider(): array 'Free Shipping', 'Free', ['value' => 0, 'currency' => 'USD'], - ['value' => 0, 'currency' => 'USD'], ], ]; } @@ -158,10 +150,6 @@ private function getQuery( value currency } - base_amount { - value - currency - } } } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php index 946c66a809389..117aedf59b5a5 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php @@ -77,14 +77,6 @@ public function testSetShippingMethodOnCartWithSimpleProduct() self::assertEquals(10, $amount['value']); self::assertArrayHasKey('currency', $amount); self::assertEquals('USD', $amount['currency']); - - self::assertArrayHasKey('base_amount', $shippingAddress['selected_shipping_method']); - $baseAmount = $shippingAddress['selected_shipping_method']['base_amount']; - - self::assertArrayHasKey('value', $baseAmount); - self::assertEquals(10, $baseAmount['value']); - self::assertArrayHasKey('currency', $baseAmount); - self::assertEquals('USD', $baseAmount['currency']); } /** @@ -390,10 +382,6 @@ private function getQuery( value currency } - base_amount { - value - currency - } } } } From 9395f55625c78fe51a96d0300a0a9d1fdc6fc511 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 4 Sep 2019 12:21:28 +0300 Subject: [PATCH 608/841] MC-19716: Cannot change action settings of scheduled update for cart rule --- .../Controller/Adminhtml/Promo/Quote/NewActionHtml.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php index 89a0d6e579727..de678851e385c 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php @@ -16,7 +16,7 @@ class NewActionHtml extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote public function execute() { $id = $this->getRequest()->getParam('id'); - $formName = $this->getRequest()->getParam('form'); + $formName = $this->getRequest()->getParam('form_namespace'); $typeArr = explode('|', str_replace('-', '/', $this->getRequest()->getParam('type'))); $type = $typeArr[0]; @@ -37,6 +37,7 @@ public function execute() if ($model instanceof \Magento\Rule\Model\Condition\AbstractCondition) { $model->setJsFormObject($formName); + $model->setFormName($formName); $html = $model->asHtmlRecursive(); } else { $html = ''; From 882bbaec90da72afbe74ca4540d332146da8536a Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Wed, 4 Sep 2019 12:37:12 +0300 Subject: [PATCH 609/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Change fetchRow to fetchOne. --- app/code/Magento/Catalog/Model/ResourceModel/Category.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index b6f565f5fae4e..fb8c089ed9f9a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -1148,14 +1148,13 @@ public function getCategoryWithChildren(int $categoryId): array { $connection = $this->getConnection(); - $select = $connection->select() + $selectAttributeCode = $connection->select() ->from( ['eav_attribute' => $this->getTable('eav_attribute')], ['attribute_id'] )->where('entity_type_id = ?', CategorySetup::CATEGORY_ENTITY_TYPE_ID) ->where('attribute_code = ?', 'is_anchor') ->limit(1); - $attributeId = $connection->fetchRow($select); $select = $connection->select() ->from( @@ -1167,7 +1166,7 @@ public function getCategoryWithChildren(int $categoryId): array ['is_anchor' => 'cce_int.value'] )->where( 'cce_int.attribute_id = ?', - $attributeId['attribute_id'] + $connection->fetchOne($selectAttributeCode) )->where( "cce.path LIKE '%/{$categoryId}' OR cce.path LIKE '%/{$categoryId}/%'" )->order('path'); From 007da3290c5c3c8899f021279d2d034b8bcaf649 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Wed, 4 Sep 2019 13:54:27 +0300 Subject: [PATCH 610/841] Adjusting the MTF tests --- .../AssertUrlValidationErrorGenerated.php | 53 +++++++++++++++++++ .../TestCase/CreateIntegrationEntityTest.xml | 30 ++++------- 2 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertUrlValidationErrorGenerated.php diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertUrlValidationErrorGenerated.php b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertUrlValidationErrorGenerated.php new file mode 100644 index 0000000000000..2fabecd2a920c --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/Constraint/AssertUrlValidationErrorGenerated.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Integration\Test\Constraint; + +use Magento\Integration\Test\Page\Adminhtml\IntegrationNew; +use Magento\Integration\Test\Fixture\Integration; +use Magento\Mtf\Constraint\AbstractConstraint; +use PHPUnit\Framework\Assert; + +/** + * Assert validation error generated when saving integration with invalid callback url. + */ +class AssertUrlValidationErrorGenerated extends AbstractConstraint +{ + /** + * Assert validation error generated when saving integration with invalid email. + * + * @param IntegrationNew $integrationNew + * @param Integration $integration + * @return void + */ + public function processAssert( + IntegrationNew $integrationNew, + Integration $integration + ) { + $errors = $integrationNew->getIntegrationForm()->getJsErrors("integration_info"); + $urlJsError = false; + foreach ($errors as $error) { + if (strpos($error, 'Please enter a valid URL.') !== false) { + $urlJsError = true; + break; + } + } + Assert::assertTrue( + $urlJsError, + 'Failed to validate callback url (' . $integration->getEndpoint() . ') when saving integration.' + ); + } + + /** + * Returns a string representation of successful assertion. + * + * @return string + */ + public function toString() + { + return 'Callback url is properly validated when saving integration.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml index 711de1ac31bb5..e9d1a94dbbd90 100644 --- a/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Integration/Test/TestCase/CreateIntegrationEntityTest.xml @@ -43,10 +43,8 @@ <data name="integration/data/identity_link_url" xsi:type="string"><script>alert('XSS')</script></data> <data name="integration/data/current_password" xsi:type="string">%current_password%</data> <data name="integration/data/resource_access" xsi:type="string">All</data> - <constraint name="Magento\Integration\Test\Constraint\AssertNoAlertPopup" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessage" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationForm" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationInGrid" /> + <constraint name="Magento\Integration\Test\Constraint\AssertUrlValidationErrorGenerated" /> + <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessageNotPresent" /> </variation> <variation name="CreateIntegrationEntityTestVariation5" summary="Input fields with javascript tag" ticketId="MAGETWO-16819"> <data name="integration/data/name" xsi:type="string"><IMG SRC=javascript:alert('XSS-%isolation%')></data> @@ -55,10 +53,8 @@ <data name="integration/data/identity_link_url" xsi:type="string"><IMG SRC=javascript:alert('XSS')></data> <data name="integration/data/current_password" xsi:type="string">%current_password%</data> <data name="integration/data/resource_access" xsi:type="string">All</data> - <constraint name="Magento\Integration\Test\Constraint\AssertNoAlertPopup" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessage" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationForm" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationInGrid" /> + <constraint name="Magento\Integration\Test\Constraint\AssertUrlValidationErrorGenerated" /> + <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessageNotPresent" /> </variation> <variation name="CreateIntegrationEntityTestVariation6" summary="Input fields with single quote" ticketId="MAGETWO-16820"> <data name="integration/data/name" xsi:type="string">name-%isolation%' OR 'a'='a</data> @@ -67,10 +63,8 @@ <data name="integration/data/identity_link_url" xsi:type="string">link' OR 'a'='a</data> <data name="integration/data/current_password" xsi:type="string">%current_password%</data> <data name="integration/data/resource_access" xsi:type="string">All</data> - <constraint name="Magento\Integration\Test\Constraint\AssertNoAlertPopup" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessage" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationForm" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationInGrid" /> + <constraint name="Magento\Integration\Test\Constraint\AssertUrlValidationErrorGenerated" /> + <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessageNotPresent" /> </variation> <variation name="CreateIntegrationEntityTestVariation7" summary="Input fields with double quote" ticketId="MAGETWO-16820"> <data name="integration/data/name" xsi:type="string">name-%isolation%" OR "a"="a</data> @@ -79,10 +73,8 @@ <data name="integration/data/identity_link_url" xsi:type="string">link" OR "a"="a</data> <data name="integration/data/current_password" xsi:type="string">%current_password%</data> <data name="integration/data/resource_access" xsi:type="string">All</data> - <constraint name="Magento\Integration\Test\Constraint\AssertNoAlertPopup" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessage" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationForm" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationInGrid" /> + <constraint name="Magento\Integration\Test\Constraint\AssertUrlValidationErrorGenerated" /> + <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessageNotPresent" /> </variation> <variation name="CreateIntegrationEntityTestVariation8" summary="Input fields with single and double quote" ticketId="MAGETWO-16820"> <data name="integration/data/name" xsi:type="string">name-%isolation%" OR 'a"='a</data> @@ -91,10 +83,8 @@ <data name="integration/data/identity_link_url" xsi:type="string">link" OR 'a"='a</data> <data name="integration/data/current_password" xsi:type="string">%current_password%</data> <data name="integration/data/resource_access" xsi:type="string">All</data> - <constraint name="Magento\Integration\Test\Constraint\AssertNoAlertPopup" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessage" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationForm" /> - <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationInGrid" /> + <constraint name="Magento\Integration\Test\Constraint\AssertUrlValidationErrorGenerated" /> + <constraint name="Magento\Integration\Test\Constraint\AssertIntegrationSuccessSaveMessageNotPresent" /> </variation> <variation name="CreateIntegrationEntityTestVariation9" summary="Invalid Email: abc.example.com" ticketId="MAGETWO-16755"> <data name="integration/data/name" xsi:type="string">Integration%isolation%</data> From 976bd23483deb6a02c5e2eb2ee8754e834db026e Mon Sep 17 00:00:00 2001 From: Nikita Chubukov <nikita_chubukov@epam.com> Date: Wed, 4 Sep 2019 12:37:37 +0300 Subject: [PATCH 611/841] MC-15256: Exported customer without modification can not be imported - Fix CR comments --- .../CustomerImportExport/Model/Import/CustomerTest.php | 2 +- .../Model/Import/_files/customers_to_import.csv | 4 ++-- .../Import/_files/customers_with_gender_to_import.csv | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_with_gender_to_import.csv 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 daab687408749..77ceae27e0774 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/CustomerTest.php @@ -78,7 +78,7 @@ public function testImportData() $expectAddedCustomers = 5; $source = new \Magento\ImportExport\Model\Import\Source\Csv( - __DIR__ . '/_files/customers_to_import.csv', + __DIR__ . '/_files/customers_with_gender_to_import.csv', $this->directoryWrite ); diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv index 96c14c67607aa..30a283ce0502f 100644 --- a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_to_import.csv @@ -1,7 +1,7 @@ email,_website,_store,confirmation,created_at,created_in,default_billing,default_shipping,disable_auto_group_change,dob,firstname,gender,group_id,lastname,middlename,password_hash,prefix,rp_token,rp_token_created_at,store_id,suffix,taxvat,website_id,password -AnthonyANealy@magento.com,base,admin,,5/6/2012 15:53,Admin,1,1,0,5/6/2010,Anthony,Female,1,Nealy,A.,6a9c9bfb2ba88a6ad2a64e7402df44a763e0c48cd21d7af9e7e796cd4677ee28:RF,,,,0,,,1, +AnthonyANealy@magento.com,base,admin,,5/6/2012 15:53,Admin,1,1,0,5/6/2010,Anthony,Male,1,Nealy,A.,6a9c9bfb2ba88a6ad2a64e7402df44a763e0c48cd21d7af9e7e796cd4677ee28:RF,,,,0,,,1, LoriBBanks@magento.com,admin,admin,,5/6/2012 15:59,Admin,3,3,0,5/6/2010,Lori,Female,1,Banks,B.,7ad6dbdc83d3e9f598825dc58b84678c7351e4281f6bc2b277a32dcd88b9756b:pz,,,,0,,,0, CharlesTAlston@teleworm.us,base,admin,,5/6/2012 16:13,Admin,4,4,0,,Jhon,Female,1,Doe,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, -customer@example.com,base,admin,,5/6/2012 16:15,Admin,4,4,0,,Firstname,Female,1,Lastname,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, +customer@example.com,base,admin,,5/6/2012 16:15,Admin,4,4,0,,Firstname,Male,1,Lastname,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, julie.worrell@example.com,base,admin,,5/6/2012 16:19,Admin,4,4,0,,Julie,Female,1,Worrell,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, david.lamar@example.com,base,admin,,5/6/2012 16:25,Admin,4,4,0,,David,,1,Lamar,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, diff --git a/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_with_gender_to_import.csv b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_with_gender_to_import.csv new file mode 100644 index 0000000000000..96c14c67607aa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CustomerImportExport/Model/Import/_files/customers_with_gender_to_import.csv @@ -0,0 +1,7 @@ +email,_website,_store,confirmation,created_at,created_in,default_billing,default_shipping,disable_auto_group_change,dob,firstname,gender,group_id,lastname,middlename,password_hash,prefix,rp_token,rp_token_created_at,store_id,suffix,taxvat,website_id,password +AnthonyANealy@magento.com,base,admin,,5/6/2012 15:53,Admin,1,1,0,5/6/2010,Anthony,Female,1,Nealy,A.,6a9c9bfb2ba88a6ad2a64e7402df44a763e0c48cd21d7af9e7e796cd4677ee28:RF,,,,0,,,1, +LoriBBanks@magento.com,admin,admin,,5/6/2012 15:59,Admin,3,3,0,5/6/2010,Lori,Female,1,Banks,B.,7ad6dbdc83d3e9f598825dc58b84678c7351e4281f6bc2b277a32dcd88b9756b:pz,,,,0,,,0, +CharlesTAlston@teleworm.us,base,admin,,5/6/2012 16:13,Admin,4,4,0,,Jhon,Female,1,Doe,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, +customer@example.com,base,admin,,5/6/2012 16:15,Admin,4,4,0,,Firstname,Female,1,Lastname,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, +julie.worrell@example.com,base,admin,,5/6/2012 16:19,Admin,4,4,0,,Julie,Female,1,Worrell,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, +david.lamar@example.com,base,admin,,5/6/2012 16:25,Admin,4,4,0,,David,,1,Lamar,T.,145d12bfff8a6a279eb61e277e3d727c0ba95acc1131237f1594ddbb7687a564:l1,,,,0,,,2, From bfa0278c65aec94bd1e256f10e9fb6cf73cb875c Mon Sep 17 00:00:00 2001 From: Vitaliy Boyko <v.boyko@atwix.com> Date: Wed, 4 Sep 2019 16:15:46 +0300 Subject: [PATCH 612/841] graphQl-891: refactored the customer notes field in the shipping address --- .../QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php | 4 ++++ app/code/Magento/QuoteGraphQl/etc/schema.graphqls | 4 +++- .../GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php | 5 ++++- .../GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php | 5 ++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php index 77719bed5b16f..260f1343556f0 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php @@ -53,6 +53,10 @@ public function execute(ContextInterface $context, CartInterface $cart, array $s $customerAddressId = $shippingAddressInput['customer_address_id'] ?? null; $addressInput = $shippingAddressInput['address'] ?? null; + if ($addressInput) { + $addressInput['customer_notes'] = $shippingAddressInput['customer_notes'] ?? ''; + } + if (null === $customerAddressId && null === $addressInput) { throw new GraphQlInputException( __('The shipping address must contain either "customer_address_id" or "address".') diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index c458b5e9dc05d..d357ae4de4d26 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -85,6 +85,7 @@ input SetShippingAddressesOnCartInput { input ShippingAddressInput { customer_address_id: Int # If provided then will be used address from address book address: CartAddressInput + customer_notes: String } input SetBillingAddressOnCartInput { @@ -210,7 +211,6 @@ interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Mo postcode: String country: CartAddressCountry telephone: String - customer_notes: String } type ShippingCartAddress implements CartAddressInterface { @@ -218,9 +218,11 @@ type ShippingCartAddress implements CartAddressInterface { selected_shipping_method: SelectedShippingMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddress\\SelectedShippingMethod") items_weight: Float cart_items: [CartItemQuantity] + customer_notes: String } type BillingCartAddress implements CartAddressInterface { + customer_notes: String @deprecated (reason: "The field is used only in shipping address") } type CartItemQuantity { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php index 15ee125955062..e74b7c41b3983 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php @@ -84,6 +84,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() telephone: "88776655" save_in_address_book: false } + customer_notes: "Test note" } ] } @@ -102,6 +103,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() code } __typename + customer_notes } } } @@ -671,7 +673,8 @@ private function assertNewShippingAddressFields(array $shippingAddressResponse): ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], - ['response_field' => '__typename', 'expected_value' => 'ShippingCartAddress'] + ['response_field' => '__typename', 'expected_value' => 'ShippingCartAddress'], + ['response_field' => 'customer_notes', 'expected_value' => 'Test note'] ]; $this->assertResponseFields($shippingAddressResponse, $assertionMap); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php index 537c8f09a0a98..0351a4f58a8e0 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php @@ -55,6 +55,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() telephone: "88776655" save_in_address_book: false } + customer_notes: "Test note" } ] } @@ -73,6 +74,7 @@ public function testSetNewShippingAddressOnCartWithSimpleProduct() label } __typename + customer_notes } } } @@ -527,7 +529,8 @@ private function assertNewShippingAddressFields(array $shippingAddressResponse): ['response_field' => 'postcode', 'expected_value' => '887766'], ['response_field' => 'telephone', 'expected_value' => '88776655'], ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], - ['response_field' => '__typename', 'expected_value' => 'ShippingCartAddress'] + ['response_field' => '__typename', 'expected_value' => 'ShippingCartAddress'], + ['response_field' => 'customer_notes', 'expected_value' => 'Test note'] ]; $this->assertResponseFields($shippingAddressResponse, $assertionMap); From 58377dea35472fd010a60ff93de0174ae83eb263 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Wed, 4 Sep 2019 08:28:17 -0500 Subject: [PATCH 613/841] MC-19706: URL rewrite not created upon creating the product --- app/code/Magento/Catalog/Model/Product.php | 8 +-- .../Catalog/Test/Unit/Model/ProductTest.php | 57 +++++++++++++++++-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 51822523957bf..ddef48aa75975 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -832,12 +832,12 @@ public function getStoreIds() if (!$this->hasStoreIds()) { $storeIds = []; if ($websiteIds = $this->getWebsiteIds()) { - if ($this->_storeManager->isSingleStoreMode() && !$this->isObjectNew()) { + if (!$this->isObjectNew() && $this->_storeManager->isSingleStoreMode()) { $websiteIds = array_keys($websiteIds); } foreach ($websiteIds as $websiteId) { $websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds(); - $storeIds = array_merge($storeIds, $websiteStores); + $storeIds = $storeIds + $websiteStores; } } $this->setStoreIds($storeIds); @@ -920,9 +920,9 @@ public function beforeSave() //Validate changing of design. $userType = $this->getUserContext()->getUserType(); if (( - $userType === UserContextInterface::USER_TYPE_ADMIN + $userType === UserContextInterface::USER_TYPE_ADMIN || $userType === UserContextInterface::USER_TYPE_INTEGRATION - ) + ) && !$this->getAuthorization()->isAllowed('Magento_Catalog::edit_product_design') ) { $this->setData('custom_design', $this->getOrigData('custom_design')); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index 8bf8473080c54..350b15fe3c18b 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -13,6 +13,7 @@ use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Framework\Api\ExtensionAttributesFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Store\Model\StoreManagerInterface; /** * Product Test @@ -207,6 +208,11 @@ class ProductTest extends \PHPUnit\Framework\TestCase */ private $eavConfig; + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManager; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -303,13 +309,13 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $storeManager->expects($this->any()) + $this->storeManager->expects($this->any()) ->method('getStore') ->will($this->returnValue($this->store)); - $storeManager->expects($this->any()) + $this->storeManager->expects($this->any()) ->method('getWebsite') ->will($this->returnValue($this->website)); $this->indexerRegistryMock = $this->createPartialMock( @@ -394,7 +400,7 @@ protected function setUp() 'extensionFactory' => $this->extensionAttributesFactory, 'productPriceIndexerProcessor' => $this->productPriceProcessor, 'catalogProductOptionFactory' => $optionFactory, - 'storeManager' => $storeManager, + 'storeManager' => $this->storeManager, 'resource' => $this->resource, 'registry' => $this->registry, 'moduleManager' => $this->moduleManager, @@ -450,6 +456,46 @@ public function testGetStoreIds() $this->assertEquals($expectedStoreIds, $this->model->getStoreIds()); } + /** + * @dataProvider getSingleStoreIds + * @param bool $isObjectNew + */ + public function testGetStoreSingleSiteModelIds( + bool $isObjectNew + ) { + $websiteIDs = [0 => 2]; + $this->model->setWebsiteIds( + !$isObjectNew ? $websiteIDs : array_flip($websiteIDs) + ); + + $this->model->isObjectNew($isObjectNew); + + $this->storeManager->expects($this->exactly( + (int) !$isObjectNew + )) + ->method('isSingleStoreMode') + ->will($this->returnValue(true)); + + $this->website->expects( + $this->once() + )->method('getStoreIds') + ->will($this->returnValue($websiteIDs)); + + $this->assertEquals($websiteIDs, $this->model->getStoreIds()); + } + + public function getSingleStoreIds() + { + return [ + [ + false + ], + [ + true + ], + ]; + } + public function testGetStoreId() { $this->model->setStoreId(3); @@ -1221,8 +1267,7 @@ public function testGetMediaGalleryImagesMerging() { $mediaEntries = [ - 'images' => - [ + 'images' => [ [ 'value_id' => 1, 'file' => 'imageFile.jpg', From ba0fb9b27bb0a09625f09b110796b02cbc9b7850 Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Wed, 4 Sep 2019 16:21:42 +0300 Subject: [PATCH 614/841] magento/magento2#: Incorrect annotation in the Magento/Framework/MessageQueue/etc/queue.xsd schema --- lib/internal/Magento/Framework/MessageQueue/etc/queue.xsd | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/internal/Magento/Framework/MessageQueue/etc/queue.xsd b/lib/internal/Magento/Framework/MessageQueue/etc/queue.xsd index 7c018403d176d..d27c2cd8b7025 100644 --- a/lib/internal/Magento/Framework/MessageQueue/etc/queue.xsd +++ b/lib/internal/Magento/Framework/MessageQueue/etc/queue.xsd @@ -11,6 +11,7 @@ <xs:annotation> <xs:documentation> @deprecated + Deprecated for RabbitMQ connection. </xs:documentation> </xs:annotation> <xs:complexType> From 3557f7794f4bac725615560a04efc3e7865f45b1 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 4 Sep 2019 16:54:49 +0300 Subject: [PATCH 615/841] MC-19716: Cannot change action settings of scheduled update for cart rule --- .../Adminhtml/Promo/Quote/NewActionHtml.php | 10 +++-- .../Promo/Quote/NewActionHtmlTest.php | 41 +++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php index de678851e385c..45c0c5fab87ae 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php @@ -1,12 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -class NewActionHtml extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\SalesRule\Controller\Adminhtml\Promo\Quote; + +/** + * New action html action + */ +class NewActionHtml extends Quote implements HttpPostActionInterface { /** * New action html action @@ -37,7 +42,6 @@ public function execute() if ($model instanceof \Magento\Rule\Model\Condition\AbstractCondition) { $model->setJsFormObject($formName); - $model->setFormName($formName); $html = $model->asHtmlRecursive(); } else { $html = ''; diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php new file mode 100644 index 0000000000000..c010ec3e92f74 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * New action html test + */ +class NewActionHtmlTest extends AbstractBackendController +{ + /** + * Test verifies that execute method has the proper data-form-part value in html response + * + * @return void + */ + public function testExecute(): void + { + $formName = 'test_form'; + $this->getRequest()->setParams( + [ + 'id' => 1, + 'form_namespace' => $formName, + 'type' => 'Magento\SalesRule\Model\Rule\Condition\Product|quote_item_price', + ] + ); + $objectManager = Bootstrap::getObjectManager(); + /** @var NewActionHtml $controller */ + $controller = $objectManager->create(NewActionHtml::class); + $controller->execute(); + $html = $this->getResponse() + ->getBody(); + $this->assertContains($formName, $html); + } +} From c094e33782221fc07ee63a9612d29a6d807b3f26 Mon Sep 17 00:00:00 2001 From: Viktor Petryk <victor.petryk@transoftgroup.com> Date: Wed, 4 Sep 2019 17:05:26 +0300 Subject: [PATCH 616/841] MC-19849: Full reindex of "catalogsearch_fulltext" leads to delete real index DB table --- .../CatalogSearch/Model/Indexer/Fulltext.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php index 21d8b7297da7d..912dec8666191 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php @@ -3,11 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogSearch\Model\Indexer; use Magento\CatalogSearch\Model\Indexer\Fulltext\Action\FullFactory; +use Magento\CatalogSearch\Model\Indexer\Scope\State; use Magento\CatalogSearch\Model\Indexer\Scope\StateFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext as FulltextResource; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Indexer\DimensionProviderInterface; use Magento\Store\Model\StoreDimensionProvider; use Magento\Indexer\Model\ProcessManager; @@ -79,6 +82,7 @@ class Fulltext implements * @param DimensionProviderInterface $dimensionProvider * @param array $data * @param ProcessManager $processManager + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( FullFactory $fullActionFactory, @@ -95,11 +99,9 @@ public function __construct( $this->fulltextResource = $fulltextResource; $this->data = $data; $this->indexSwitcher = $indexSwitcher; - $this->indexScopeState = $indexScopeStateFactory->create(); + $this->indexScopeState = ObjectManager::getInstance()->get(State::class); $this->dimensionProvider = $dimensionProvider; - $this->processManager = $processManager ?: \Magento\Framework\App\ObjectManager::getInstance()->get( - ProcessManager::class - ); + $this->processManager = $processManager ?: ObjectManager::getInstance()->get(ProcessManager::class); } /** @@ -127,9 +129,11 @@ public function executeByDimensions(array $dimensions, \Traversable $entityIds = throw new \InvalidArgumentException('Indexer "' . self::INDEXER_ID . '" support only Store dimension'); } $storeId = $dimensions[StoreDimensionProvider::DIMENSION_NAME]->getValue(); - $saveHandler = $this->indexerHandlerFactory->create([ - 'data' => $this->data - ]); + $saveHandler = $this->indexerHandlerFactory->create( + [ + 'data' => $this->data, + ] + ); if (null === $entityIds) { $this->indexScopeState->useTemporaryIndex(); From cd8f6bff21f80b91723d42398c834fc3a3777ff6 Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Wed, 4 Sep 2019 19:48:08 +0530 Subject: [PATCH 617/841] product back redirect navigate from customer view cart product --- .../Adminhtml/Product/Edit/Button/Back.php | 19 ++++++++++++++++++- .../Block/Adminhtml/Edit/Tab/Cart.php | 14 ++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Button/Back.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Button/Back.php index db42bb66c9bd1..60e17599f6dec 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Button/Back.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Button/Back.php @@ -11,15 +11,32 @@ class Back extends Generic { /** + * Get Button Data + * * @return array */ public function getButtonData() { return [ 'label' => __('Back'), - 'on_click' => sprintf("location.href = '%s';", $this->getUrl('*/*/')), + 'on_click' => sprintf("location.href = '%s';", $this->getBackUrl()), 'class' => 'back', 'sort_order' => 10 ]; } + /** + * Get URL for back + * + * @return string + */ + private function getBackUrl() + { + if ($this->context->getRequestParam('customerId')) { + return $this->getUrl( + 'customer/index/edit', + ['id' => $this->context->getRequestParam('customerId')] + ); + } + return $this->getUrl('*/*/'); + } } diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php index db560f7de3ecb..3709f4914c477 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Cart.php @@ -75,7 +75,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function _construct() { @@ -119,7 +119,7 @@ protected function _prepareCollection() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _prepareColumns() { @@ -201,7 +201,7 @@ public function getCustomerId() } /** - * {@inheritdoc} + * @inheritdoc */ public function getGridUrl() { @@ -224,7 +224,13 @@ public function getGridParentHtml() */ public function getRowUrl($row) { - return $this->getUrl('catalog/product/edit', ['id' => $row->getProductId()]); + return $this->getUrl( + 'catalog/product/edit', + [ + 'id' => $row->getProductId(), + 'customerId' => $this->getCustomerId() + ] + ); } /** From 8411a5c4f253a4d1561597b71aa83c8ad82c2873 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko <lytvynen@adobe.com> Date: Wed, 4 Sep 2019 09:56:10 -0500 Subject: [PATCH 618/841] MC-19706: URL rewrite not created upon creating the product --- app/code/Magento/Catalog/Model/Product.php | 4 +++- app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index ddef48aa75975..1b7552c82276d 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -837,7 +837,9 @@ public function getStoreIds() } foreach ($websiteIds as $websiteId) { $websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds(); - $storeIds = $storeIds + $websiteStores; + foreach ($websiteStores as $websiteStore) { + $storeIds []= $websiteStore; + } } } $this->setStoreIds($storeIds); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index 350b15fe3c18b..5eaf2422e95a9 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -470,9 +470,11 @@ public function testGetStoreSingleSiteModelIds( $this->model->isObjectNew($isObjectNew); - $this->storeManager->expects($this->exactly( - (int) !$isObjectNew - )) + $this->storeManager->expects( + $this->exactly( + (int) !$isObjectNew + ) + ) ->method('isSingleStoreMode') ->will($this->returnValue(true)); From 71d1ebc5d9287f885f07ca75060d86a4a03f678a Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Wed, 4 Sep 2019 10:14:43 -0500 Subject: [PATCH 619/841] Semantic build failed --- app/code/Magento/Wishlist/Model/Wishlist.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/Wishlist.php b/app/code/Magento/Wishlist/Model/Wishlist.php index 827607e72f807..21a900b234390 100644 --- a/app/code/Magento/Wishlist/Model/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Wishlist.php @@ -370,7 +370,8 @@ protected function _addCatalogProduct(Product $product, $qty = 1, $forciblySetQt /** * Retrieve wishlist item collection * - * @return ResourceModel\Item\Collection + * @return \Magento\Wishlist\Model\ResourceModel\Item\Collection + * @throws NoSuchEntityException */ public function getItemCollection() { @@ -606,7 +607,7 @@ public function setSharedStoreIds($storeIds) /** * Retrieve wishlist store object * - * @return Store + * @return \Magento\Store\Model\Store * @throws NoSuchEntityException */ public function getStore() From aa55e958b40dabdb839ae413cdebebe49e0df4e3 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Wed, 4 Sep 2019 11:11:09 -0500 Subject: [PATCH 620/841] MC-19791: Poor performance on sales order update - string to integer --- .../Magento/Framework/Model/ResourceModel/Db/AbstractDb.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index fc0edf931fa9c..67cd6679de98d 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -793,7 +793,10 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) */ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) { - $condition = $this->getConnection()->quoteInto($this->getIdFieldName() . '=?', $object->getId()); + //quoting integer values as strings may decrease query performance on some environments + $condition = is_int($object->getId()) + ? $this->getIdFieldName() . '=' . (int) $object->getId() + : $this->getConnection()->quoteInto($this->getIdFieldName() . '=?', $object->getId()); /** * Not auto increment primary key support */ From 6cbe1f482913d24e15e4b6df1549bae5bc49a7e2 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Wed, 4 Sep 2019 11:16:25 -0500 Subject: [PATCH 621/841] MC-19791: Poor performance on sales order update - string to integer --- .../Magento/Framework/Model/ResourceModel/Db/AbstractDb.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 67cd6679de98d..3fd44db548149 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -793,8 +793,8 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) */ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) { - //quoting integer values as strings may decrease query performance on some environments - $condition = is_int($object->getId()) + //quoting numeric values as strings may decrease query performance on some environments + $condition = is_numeric($object->getId()) ? $this->getIdFieldName() . '=' . (int) $object->getId() : $this->getConnection()->quoteInto($this->getIdFieldName() . '=?', $object->getId()); /** From 68b4a6be5aa39bb3ceaf678882b0d1dbc09fe8b8 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 4 Sep 2019 11:17:26 -0500 Subject: [PATCH 622/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed codestyle --- .../TestFramework/Dependency/PhpRule.php | 18 +++++------- .../Dependency/Route/RouteMapper.php | 9 ------ .../Magento/Test/Integrity/DependencyTest.php | 28 ++++++++++--------- .../dependency_test/whitelist/routes_ce.php | 2 +- 4 files changed, 23 insertions(+), 34 deletions(-) diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php index 903b1da0ba9ad..ae59e4a017520 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php @@ -285,12 +285,11 @@ private function isPluginDependency($dependent, $dependency) * @return array * @throws LocalizedException * @throws \Exception - * @SuppressWarnings(PMD.CyclomaticComplexity) */ protected function _caseGetUrl(string $currentModule, string &$contents): array { $pattern = '#(\->|:)(?<source>getUrl\(([\'"])(?<route_id>[a-z0-9\-_]{3,}|\*)' - .'(/(?<controller_name>[a-z0-9\-_]+|\*))?(/(?<action_name>[a-z0-9\-_]+))?\3|\*)#i'; + .'(\/(?<controller_name>[a-z0-9\-_]+|\*))?(\/(?<action_name>[a-z0-9\-_]+|\*))?\3)#i'; $dependencies = []; if (!preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER)) { @@ -302,18 +301,13 @@ protected function _caseGetUrl(string $currentModule, string &$contents): array $routeId = $item['route_id']; $controllerName = $item['controller_name'] ?? UrlInterface::DEFAULT_CONTROLLER_NAME; $actionName = $item['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME; - if (in_array( - implode('/', [$routeId, $controllerName, $actionName]), - $this->getRoutesWhitelist() - )) { - continue; - } + // skip rest - if ($routeId == "rest") { //MC-17627 + if ($routeId === "rest") { //MC-17627 continue; } // skip wildcards - if ($routeId == "*" || $controllerName == "*" || $actionName == "*") { //MC-17627 + if ($routeId === "*" || $controllerName === "*" || $actionName === "*") { //MC-17627 continue; } $modules = $this->routeMapper->getDependencyByRoutePath( @@ -333,7 +327,9 @@ protected function _caseGetUrl(string $currentModule, string &$contents): array } } } catch (NoSuchActionException $e) { - throw new LocalizedException(__('Invalid URL path: %1', $e->getMessage()), $e); + if (array_search($e->getMessage(), $this->getRoutesWhitelist()) === false) { + throw new LocalizedException(__('Invalid URL path: %1', $e->getMessage()), $e); + } } return $dependencies; diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php index 8bd6b1479768f..1b93adb5f03ef 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php @@ -243,15 +243,6 @@ private function processConfigFile(string $module, string $configFile) if (!in_array($module, $this->routers[$routerId][$routeId])) { $this->routers[$routerId][$routeId][] = $module; } - if (isset($route['frontName'])) { - $frontName = (string)$route['frontName']; - if (!isset($this->routers[$routerId][$frontName])) { - $this->routers[$routerId][$frontName] = []; - } - if (!in_array($module, $this->routers[$routerId][$frontName])) { - $this->routers[$routerId][$frontName][] = $module; - } - } } } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index d3160be7c72c0..c381aa6b908d8 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -234,7 +234,8 @@ protected static function _initRules() . '/_files/dependency_test/tables_*.php'; $dbRuleTables = []; foreach (glob($replaceFilePattern) as $fileName) { - $dbRuleTables = array_merge($dbRuleTables, @include $fileName); //phpcs:ignore + //phpcs:ignore Magento2.Performance.ForeachArrayMerge + $dbRuleTables = array_merge($dbRuleTables, @include $fileName); } self::$_rulesInstances = [ new PhpRule( @@ -264,14 +265,14 @@ private static function getRoutesWhitelist(): array { if (is_null(self::$routesWhitelist)) { $routesWhitelistFilePattern = realpath(__DIR__) . '/_files/dependency_test/whitelist/routes_*.php'; - self::$routesWhitelist = array_merge( - ...array_map( - function ($fileName) { - return include $fileName; - }, - glob($routesWhitelistFilePattern) - ) - ); + self::$routesWhitelist = []; + foreach (glob($routesWhitelistFilePattern) as $fileName) { + //phpcs:ignore Magento2.Performance.ForeachArrayMerge + self::$routesWhitelist = array_merge( + self::$routesWhitelist, + include $fileName + ); + } } return self::$routesWhitelist; } @@ -294,7 +295,7 @@ protected function _getCleanedFileContents($fileType, $file) return preg_replace( '~\<!\-\-/.*?\-\-\>~s', '', - (string)file_get_contents($file) + file_get_contents($file) ); case 'template': $contents = php_strip_whitespace($file); @@ -310,7 +311,7 @@ function ($matches) use ($contents, &$contentsWithoutHtml) { ); return $contentsWithoutHtml; } - return (string)file_get_contents($file); + return file_get_contents($file); } /** @@ -389,7 +390,8 @@ protected function getDependenciesFromFiles($module, $fileType, $file, $contents foreach (self::$_rulesInstances as $rule) { /** @var \Magento\TestFramework\Dependency\RuleInterface $rule */ $newDependencies = $rule->getDependencyInfo($module, $fileType, $file, $contents); - $dependencies = array_merge($dependencies, $newDependencies); //phpcs:ignore + //phpcs:ignore Magento2.Performance.ForeachArrayMerge + $dependencies = array_merge($dependencies, $newDependencies); } foreach ($dependencies as $key => $dependency) { foreach (self::$whiteList as $namespace) { @@ -504,7 +506,7 @@ public function collectRedundant() foreach (array_keys(self::$mapDependencies) as $module) { $declared = $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_DECLARED); - //phpcs:ignore + //phpcs:ignore Magento2.Performance.ForeachArrayMerge $found = array_merge( $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_FOUND), $this->_getDependencies($module, self::TYPE_SOFT, self::MAP_TYPE_FOUND), diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php index 1aef3ffdf104a..9ebc951a3a3a1 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/whitelist/routes_ce.php @@ -6,5 +6,5 @@ declare(strict_types=1); return [ - 'privacy-policy-cookie-restriction-mode/index/index' + 'privacy-policy-cookie-restriction-mode/index/index', ]; From 4314d4b10baf74219c1caa8554a4fec50fba7191 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Wed, 4 Sep 2019 23:27:12 +0700 Subject: [PATCH 623/841] Resolve Scope icon and store view specific label text alignment issue issue24440 --- .../Sales/Block/Adminhtml/Order/Status/NewStatus/Form.php | 2 +- .../web/css/source/module/main/_store-scope.less | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Status/NewStatus/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Status/NewStatus/Form.php index c4d8907c9762b..1b275c4d809cb 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Status/NewStatus/Form.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Status/NewStatus/Form.php @@ -81,7 +81,7 @@ protected function _addStoresFieldset($model, $form) if (!$this->_storeManager->isSingleStoreMode()) { $fieldset = $form->addFieldset( 'store_labels_fieldset', - ['legend' => __('Store View Specific Labels'), 'class' => 'store-scope'] + ['legend' => __('Store View Specific Labels'), 'class' => 'store-scope no-margin-top-tooltip'] ); } else { $fieldset = $form->addFieldset( diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less index cd73d4955d176..7c9f3975ef1b3 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less @@ -42,3 +42,11 @@ } } } + +.no-margin-top-tooltip { + .admin__legend { + .admin__field-tooltip { + margin-top: 0; + } + } +} From 25a90fbf6befadd85b093d0df9cf804112e9a7ce Mon Sep 17 00:00:00 2001 From: konarshankar07 <konar.shankar2013@gmail.com> Date: Wed, 4 Sep 2019 21:58:28 +0530 Subject: [PATCH 624/841] Fixed Minicart height calculated with child item having margin --- app/code/Magento/Checkout/view/frontend/web/js/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js index f7fd0cf4f0e86..f58a560a6b3ca 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -347,7 +347,7 @@ define([ if ($(this).find('.options').length > 0) { $(this).collapsible(); } - outerHeight = $(this).outerHeight(); + outerHeight = $(this).outerHeight(true); if (counter-- > 0) { height += outerHeight; From 656abce59d6fb8743ca6bb4e95805c4cdea34792 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 4 Sep 2019 11:52:27 -0500 Subject: [PATCH 625/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed codestyle --- .../Magento/TestFramework/Dependency/Route/RouteMapper.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php index 1b93adb5f03ef..315bb2ae26b02 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/Route/RouteMapper.php @@ -174,7 +174,8 @@ public function getDependencyByRoutePath( $dependencies = []; foreach ($this->getRouterTypes() as $routerId) { if (isset($this->getActionsMap()[$routerId][$routeId][$controllerName][$actionName])) { - $dependencies = array_merge( //phpcs:ignore + //phpcs:ignore Magento2.Performance.ForeachArrayMerge + $dependencies = array_merge( $dependencies, $this->getActionsMap()[$routerId][$routeId][$controllerName][$actionName] ); From 09cd623cc03f28b2fafdc9f497e8c606c45de23e Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Wed, 4 Sep 2019 12:17:00 -0500 Subject: [PATCH 626/841] Fixed code style --- .../Ui/DataProvider/Product/Form/Modifier/BundlePrice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php index 2c64ef69604a2..92326bb1521b4 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePrice.php @@ -64,7 +64,7 @@ public function modifyMeta(array $meta) $this->arrayManager->findPath( ProductAttributeInterface::CODE_PRICE, $meta, - self::DEFAULT_GENERAL_PANEL.'/children', + self::DEFAULT_GENERAL_PANEL . '/children', 'children' ) . static::META_CONFIG_PATH, $meta, From 4b2db0f7a29e5c13cfbc06c33b401ba9d34bd0c2 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Wed, 4 Sep 2019 12:19:38 -0500 Subject: [PATCH 627/841] MC-19791: Poor performance on sales order update - string to integer --- .../Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php index b69f50cf4f341..fea17c3c0b8c2 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php @@ -489,7 +489,7 @@ public function testPrepareDataForUpdate() ->with( 'tableName', $newData, - 'idFieldName' + 'idFieldName=0' ); $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() From 817a58a52301da5957f7d0e1b70ae8a5c09f62fb Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Wed, 4 Sep 2019 12:37:34 -0500 Subject: [PATCH 628/841] Removed extra whitespaces from the configuration --- app/code/Magento/Sitemap/etc/adminhtml/system.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sitemap/etc/adminhtml/system.xml b/app/code/Magento/Sitemap/etc/adminhtml/system.xml index 320b9592f63b5..c3c9c85027354 100644 --- a/app/code/Magento/Sitemap/etc/adminhtml/system.xml +++ b/app/code/Magento/Sitemap/etc/adminhtml/system.xml @@ -83,12 +83,12 @@ <label>Sitemap File Limits</label> <field id="max_lines" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum No of URLs Per File</label> - <validate>validate-number validate-greater-than-zero</validate> + <validate>validate-number validate-greater-than-zero</validate> </field> <field id="max_file_size" translate="label comment" type="text" sortOrder="2" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Maximum File Size</label> <comment>File size in bytes.</comment> - <validate>validate-number validate-greater-than-zero</validate> + <validate>validate-number validate-greater-than-zero</validate> </field> </group> <group id="search_engines" translate="label" type="text" sortOrder="6" showInDefault="1" showInWebsite="1" showInStore="1"> From 9a1b78154950da21073ee762ccd43e3e789766c5 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Wed, 4 Sep 2019 12:41:44 -0500 Subject: [PATCH 629/841] Refactor the way for getting the stock, using the current product instead of instantiating a new stock item --- app/code/Magento/Wishlist/Model/Wishlist.php | 38 ++++++++++---------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/Wishlist.php b/app/code/Magento/Wishlist/Model/Wishlist.php index 21a900b234390..d8e244b7827d3 100644 --- a/app/code/Magento/Wishlist/Model/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Wishlist.php @@ -32,7 +32,6 @@ use Magento\Wishlist\Model\ResourceModel\Item\CollectionFactory; use Magento\Wishlist\Model\ResourceModel\Wishlist as ResourceWishlist; use Magento\Wishlist\Model\ResourceModel\Wishlist\Collection; -use Magento\CatalogInventory\Model\Stock\StockItemRepository; /** * Wishlist model @@ -148,11 +147,6 @@ class Wishlist extends AbstractModel implements IdentityInterface */ private $serializer; - /** - * @var StockItemRepository - */ - private $stockItemRepository; - /** * @var ScopeConfigInterface */ @@ -178,7 +172,6 @@ class Wishlist extends AbstractModel implements IdentityInterface * @param bool $useCurrentWebsite * @param array $data * @param Json|null $serializer - * @param StockItemRepository|null $stockItemRepository * @param ScopeConfigInterface|null $scopeConfig * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -200,7 +193,6 @@ public function __construct( $useCurrentWebsite = true, array $data = [], Json $serializer = null, - StockItemRepository $stockItemRepository = null, ScopeConfigInterface $scopeConfig = null ) { $this->_useCurrentWebsite = $useCurrentWebsite; @@ -216,9 +208,6 @@ public function __construct( $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->productRepository = $productRepository; - $this->stockItemRepository = $stockItemRepository ?: ObjectManager::getInstance()->get( - StockItemRepository::class - ); $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); } @@ -452,18 +441,13 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false } try { + /** @var Product $product */ $product = $this->productRepository->getById($productId, false, $storeId); } catch (NoSuchEntityException $e) { throw new LocalizedException(__('Cannot specify product.')); } - /** @var \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem */ - $stockItem = $this->stockItemRepository->get($productId); - $showOutOfStock = $this->scopeConfig->isSetFlag( - Configuration::XML_PATH_SHOW_OUT_OF_STOCK, - ScopeInterface::SCOPE_STORE - ); - if (!$stockItem->getIsInStock() && !$showOutOfStock) { + if ($this->isInStock($product)) { throw new LocalizedException(__('Cannot add product without stock to wishlist.')); } @@ -655,6 +639,24 @@ public function isSalable() return false; } + /** + * Retrieve if product has stock or config is set for showing out of stock products + * + * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @return bool + */ + public function isInStock(\Magento\Catalog\Api\Data\ProductInterface $product) + { + /** @var \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem */ + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $showOutOfStock = $this->scopeConfig->isSetFlag( + Configuration::XML_PATH_SHOW_OUT_OF_STOCK, + ScopeInterface::SCOPE_STORE + ); + $isInStock = $stockItem ? $stockItem->getIsInStock() : false; + return !$isInStock && !$showOutOfStock; + } + /** * Check customer is owner this wishlist * From 5400b22b97a009fcea62f2a096a260774af1c3a1 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 4 Sep 2019 13:06:53 -0500 Subject: [PATCH 630/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL --- .../LayeredNavigation/Builder/Category.php | 11 +++--- .../Plugin/Search/Request/ConfigReader.php | 2 +- .../CatalogGraphQl/etc/schema.graphqls | 36 +++++++++---------- app/code/Magento/GraphQl/etc/schema.graphqls | 10 +++--- 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php index cebafe31385ba..b0e67d72e25ba 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Category.php @@ -94,10 +94,13 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array if ($this->isBucketEmpty($bucket)) { return []; } - - $categoryIds = \array_map(function (AggregationValueInterface $value) { - return (int)$value->getValue(); - }, $bucket->getValues()); + + $categoryIds = \array_map( + function (AggregationValueInterface $value) { + return (int)$value->getValue(); + }, + $bucket->getValues() + ); $categoryIds = \array_diff($categoryIds, [$this->rootCategoryProvider->getRootCategory($storeId)]); $categoryLabels = \array_column( diff --git a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php index 02f23e59f15d7..85c18462aae49 100644 --- a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php +++ b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php @@ -142,7 +142,7 @@ private function generateRequest() case 'static': case 'text': case 'varchar': - if ($attribute->getFrontendInput() === 'multiselect') { + if (in_array($attribute->getFrontendInput(), ['select', 'multiselect'])) { $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName); $request['filters'][$filterName] = $this->generateTermFilter($filterName, $attribute); } elseif ($attribute->getAttributeCode() === 'sku') { diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 8e9471c77dc6b..a2ea18288a77a 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -282,7 +282,7 @@ type CategoryProducts @doc(description: "The category products object returned i } input ProductAttributeFilterInput @doc(description: "ProductAttributeFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { - category_id: FilterEqualTypeInput @doc(description: "Filter product by category id") + category_id: FilterEqualTypeInput @doc(description: "Filter product by category id") } input ProductFilterInput @deprecated(reason: "Attributes used in this input are hardcoded and some of them are not searcheable. Use @ProductAttributeFilterInput instead") @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { @@ -374,8 +374,8 @@ input ProductSortInput @deprecated(reason: "The attributes used in this input ar input ProductAttributeSortInput @doc(description: "ProductAttributeSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order. It's possible to sort products using searchable attributes with enabled 'Use in Filter Options' option") { - position: SortEnum @doc(description: "The position of products") - relevance: SortEnum @doc(description: "The search relevance score (default)") + relevance: SortEnum @doc(description: "Sort by the search relevance score (default).") + position: SortEnum @doc(description: "Sort by the position assigned to each product.") } type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characteristics about images and videos associated with a specific product.") { @@ -391,29 +391,29 @@ type MediaGalleryEntry @doc(description: "MediaGalleryEntry defines characterist } type LayerFilter { - name: String @doc(description: "Layered navigation filter name.") @deprecated(reason: "Use Aggregation.label instead") - request_var: String @doc(description: "Request variable name for filter query.") @deprecated(reason: "Use Aggregation.attribute_code instead") - filter_items_count: Int @doc(description: "Count of filter items in filter group.") @deprecated(reason: "Use Aggregation.count instead") - filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items.") @deprecated(reason: "Use Aggregation.options instead") + name: String @doc(description: "Layered navigation filter name.") @deprecated(reason: "Use Aggregation.label instead.") + request_var: String @doc(description: "Request variable name for filter query.") @deprecated(reason: "Use Aggregation.attribute_code instead.") + filter_items_count: Int @doc(description: "Count of filter items in filter group.") @deprecated(reason: "Use Aggregation.count instead.") + filter_items: [LayerFilterItemInterface] @doc(description: "Array of filter items.") @deprecated(reason: "Use Aggregation.options instead.") } interface LayerFilterItemInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\LayerFilterItemTypeResolverComposite") { - label: String @doc(description: "Filter label.") - value_string: String @doc(description: "Value for filter request variable to be used in query.") - items_count: Int @doc(description: "Count of items by filter.") + label: String @doc(description: "Filter label.") @deprecated(reason: "Use AggregationOption.label instead.") + value_string: String @doc(description: "Value for filter request variable to be used in query.") @deprecated(reason: "Use AggregationOption.value instead.") + items_count: Int @doc(description: "Count of items by filter.") @deprecated(reason: "Use AggregationOption.count instead.") } -type Aggregation { - count: Int @doc(description: "The number of filter items in the filter group.") - label: String @doc(description: "The filter named displayed in layered navigation.") - attribute_code: String! @doc(description: "Attribute code of the filter item.") - options: [AggregationOption] @doc(description: "Describes each aggregated filter option.") +type Aggregation @doc(description: "A bucket that contains information for each filterable option (such as price, category ID, and custom attributes).") { + count: Int @doc(description: "The number of options in the aggregation group.") + label: String @doc(description: "The aggregation display name.") + attribute_code: String! @doc(description: "Attribute code of the aggregation group.") + options: [AggregationOption] @doc(description: "Array of options for the aggregation.") } type AggregationOption { - count: Int @doc(description: "The number of items returned by the filter.") - label: String! @doc(description: "Filter label.") - value: String! @doc(description: "Value for filter request variable to be used in query.") + count: Int @doc(description: "The number of items that match the aggregation option.") + label: String @doc(description: "Aggregation option display label.") + value: String! @doc(description: "The internal ID that represents the value of the option.") } type LayerFilterItem implements LayerFilterItemInterface { diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 69b822d4285b8..90a4bd7376e45 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -66,17 +66,17 @@ input FilterTypeInput @doc(description: "FilterTypeInput specifies which action } input FilterEqualTypeInput @doc(description: "Specifies which action will be performed in a query ") { - in: [String] - eq: String + in: [String] @doc(description: "In. The value can contain a set of comma-separated values") + eq: String @doc(description: "Equals") } input FilterRangeTypeInput @doc(description: "Specifies which action will be performed in a query ") { - from: String - to: String + from: String @doc(description: "From") + to: String @doc(description: "To") } input FilterMatchTypeInput @doc(description: "Specifies which action will be performed in a query ") { - match: String + match: String @doc(description: "Match. Can be used for fuzzy matching.") } type SearchResultPageInfo @doc(description: "SearchResultPageInfo provides navigation for the query response") { From bc61b81a5075adaee491cab9345ddc301276bc87 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 4 Sep 2019 13:30:47 -0500 Subject: [PATCH 631/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed codestyle --- .../framework/Magento/TestFramework/Dependency/PhpRule.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php index ae59e4a017520..33f213454aaa4 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php @@ -285,6 +285,7 @@ private function isPluginDependency($dependent, $dependency) * @return array * @throws LocalizedException * @throws \Exception + * @SuppressWarnings(PMD.CyclomaticComplexity) */ protected function _caseGetUrl(string $currentModule, string &$contents): array { From 4e252de786e64d70c69c540c57a25b876c2bd758 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Wed, 4 Sep 2019 13:50:02 -0500 Subject: [PATCH 632/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed codestyle --- .../Magento/Test/Integrity/DependencyTest.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index c381aa6b908d8..144386df55e3a 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -286,17 +286,20 @@ private static function getRoutesWhitelist(): array */ protected function _getCleanedFileContents($fileType, $file) { + $contents = null; switch ($fileType) { case 'php': - return php_strip_whitespace($file); + $contents = php_strip_whitespace($file); + break; case 'layout': case 'config': //Removing xml comments - return preg_replace( + $contents = preg_replace( '~\<!\-\-/.*?\-\-\>~s', '', file_get_contents($file) ); + break; case 'template': $contents = php_strip_whitespace($file); //Removing html @@ -309,9 +312,12 @@ function ($matches) use ($contents, &$contentsWithoutHtml) { }, $contents ); - return $contentsWithoutHtml; + $contents = $contentsWithoutHtml; + break; + default: + $contents = file_get_contents($file); } - return file_get_contents($file); + return $contents; } /** From 964105c807e31449f41cdaba8fd79068d2daa694 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Wed, 4 Sep 2019 14:04:22 -0500 Subject: [PATCH 633/841] Another refactor, optimal way for receiving the object --- app/code/Magento/Wishlist/Model/Wishlist.php | 20 ++++++++++++---- .../Wishlist/Test/Unit/Model/WishlistTest.php | 24 +++++++++++-------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/Wishlist.php b/app/code/Magento/Wishlist/Model/Wishlist.php index d8e244b7827d3..329f3e591e25b 100644 --- a/app/code/Magento/Wishlist/Model/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Wishlist.php @@ -12,6 +12,8 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ProductFactory; +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\CatalogInventory\Model\Configuration; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; @@ -152,6 +154,11 @@ class Wishlist extends AbstractModel implements IdentityInterface */ private $scopeConfig; + /** + * @var StockRegistryInterface|null + */ + private $stockRegistry; + /** * Constructor * @@ -172,6 +179,7 @@ class Wishlist extends AbstractModel implements IdentityInterface * @param bool $useCurrentWebsite * @param array $data * @param Json|null $serializer + * @param StockRegistryInterface|null $stockRegistry * @param ScopeConfigInterface|null $scopeConfig * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -193,6 +201,7 @@ public function __construct( $useCurrentWebsite = true, array $data = [], Json $serializer = null, + StockRegistryInterface $stockRegistry = null, ScopeConfigInterface $scopeConfig = null ) { $this->_useCurrentWebsite = $useCurrentWebsite; @@ -209,6 +218,7 @@ public function __construct( parent::__construct($context, $registry, $resource, $resourceCollection, $data); $this->productRepository = $productRepository; $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(ScopeConfigInterface::class); + $this->stockRegistry = $stockRegistry ?: ObjectManager::getInstance()->get(StockRegistryInterface::class); } /** @@ -447,7 +457,7 @@ public function addNewItem($product, $buyRequest = null, $forciblySetQty = false throw new LocalizedException(__('Cannot specify product.')); } - if ($this->isInStock($product)) { + if ($this->isInStock($productId)) { throw new LocalizedException(__('Cannot add product without stock to wishlist.')); } @@ -642,13 +652,13 @@ public function isSalable() /** * Retrieve if product has stock or config is set for showing out of stock products * - * @param \Magento\Catalog\Api\Data\ProductInterface $product + * @param int $productId * @return bool */ - public function isInStock(\Magento\Catalog\Api\Data\ProductInterface $product) + private function isInStock($productId) { - /** @var \Magento\CatalogInventory\Api\Data\StockItemInterface $stockItem */ - $stockItem = $product->getExtensionAttributes()->getStockItem(); + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockRegistry->getStockItem($productId); $showOutOfStock = $this->scopeConfig->isSetFlag( Configuration::XML_PATH_SHOW_OUT_OF_STOCK, ScopeInterface::SCOPE_STORE diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php index 4d4d1a235de1d..eb788efc0d622 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Model/WishlistTest.php @@ -11,6 +11,7 @@ use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Type\AbstractType; use Magento\Catalog\Model\ProductFactory; +use Magento\CatalogInventory\Api\StockRegistryInterface; use Magento\CatalogInventory\Model\Stock\Item as StockItem; use Magento\CatalogInventory\Model\Stock\StockItemRepository; use Magento\Framework\App\Config\ScopeConfigInterface; @@ -123,12 +124,12 @@ class WishlistTest extends TestCase /** * @var StockItemRepository|PHPUnit_Framework_MockObject_MockObject */ - private $stockItemRepository; + private $scopeConfig; /** - * @var StockItemRepository|PHPUnit_Framework_MockObject_MockObject + * @var StockRegistryInterface|PHPUnit_Framework_MockObject_MockObject */ - private $scopeConfig; + private $stockRegistry; protected function setUp() { @@ -177,7 +178,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->productRepository = $this->createMock(ProductRepositoryInterface::class); - $this->stockItemRepository = $this->createMock(StockItemRepository::class); + $this->stockRegistry = $this->createMock(StockRegistryInterface::class); $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) @@ -209,7 +210,7 @@ protected function setUp() false, [], $this->serializer, - $this->stockItemRepository, + $this->stockRegistry, $this->scopeConfig ); } @@ -280,7 +281,9 @@ public function testUpdateItem($itemId, $buyRequest, $param) $stockItem = $this->getMockBuilder(StockItem::class)->disableOriginalConstructor()->getMock(); $stockItem->expects($this->any())->method('getIsInStock')->will($this->returnValue(true)); - $this->stockItemRepository->expects($this->any())->method('get')->will($this->returnValue($stockItem)); + $this->stockRegistry->expects($this->any()) + ->method('getStockItem') + ->will($this->returnValue($stockItem)); $instanceType = $this->getMockBuilder(AbstractType::class) ->disableOriginalConstructor() @@ -289,9 +292,7 @@ public function testUpdateItem($itemId, $buyRequest, $param) ->method('processConfiguration') ->will( $this->returnValue( - $this->getMockBuilder( - Product::class - )->disableOriginalConstructor()->getMock() + $this->getMockBuilder(Product::class)->disableOriginalConstructor()->getMock() ) ); @@ -413,7 +414,10 @@ function ($value) { StockItem::class )->disableOriginalConstructor()->getMock(); $stockItem->expects($this->any())->method('getIsInStock')->will($this->returnValue(true)); - $this->stockItemRepository->expects($this->any())->method('get')->will($this->returnValue($stockItem)); + + $this->stockRegistry->expects($this->any()) + ->method('getStockItem') + ->will($this->returnValue($stockItem)); $this->assertEquals($result, $this->wishlist->addNewItem($productMock, $buyRequest)); } From 1129337146a2d18c95d845dd4b3e1391b8eab27a Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Wed, 4 Sep 2019 15:11:48 -0500 Subject: [PATCH 634/841] MC-19791: Poor performance on sales order update - string to integer --- .../Test/Unit/ResourceModel/Db/AbstractDbTest.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php index fea17c3c0b8c2..4236c4f076dcb 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php @@ -429,12 +429,10 @@ public function testPrepareDataForUpdate() \Magento\Framework\Model\Context::class ); $registryMock = $this->createMock(\Magento\Framework\Registry::class); - $resourceMock = $this->createPartialMock(AbstractDb::class, [ - '_construct', - 'getConnection', - '__wakeup', - 'getIdFieldName' - ]); + $resourceMock = $this->createPartialMock( + AbstractDb::class, + ['_construct', 'getConnection', '__wakeup', 'getIdFieldName'] + ); $connectionInterfaceMock = $this->createMock(AdapterInterface::class); $resourceMock->expects($this->any()) ->method('getConnection') From aa1f52f96df32b970747ac84406adb2bb01d0189 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Wed, 4 Sep 2019 15:16:16 -0500 Subject: [PATCH 635/841] Make consts private --- .../Model/Import/AdvancedPricing.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index bc23bfdf246b5..974397226c56c 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -54,9 +54,9 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract * @deprecated * @see VALIDATOR_TIER_PRICE */ - const VALIDATOR_TEAR_PRICE = 'validator_tier_price'; + private const VALIDATOR_TEAR_PRICE = 'validator_tier_price'; - const VALIDATOR_TIER_PRICE = 'validator_tier_price'; + private const VALIDATOR_TIER_PRICE = 'validator_tier_price'; /** * Validation failure message template definitions. From b46641504f85905a92aa8be971b9aa93308862d5 Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Wed, 4 Sep 2019 18:51:53 -0500 Subject: [PATCH 636/841] MC-19735: Braintree Paypal order fails in multi address if more than one shipping address is used - Removed shipping address from authorization request for Braintree PayPal Vault since we don't allow a customer to change it on PayPal side. --- .../Request/BillingAddressDataBuilder.php | 112 ++++++++++++++++++ .../Validator/ErrorCodeProviderTest.php | 4 +- .../Braintree/etc/braintree_error_mapping.xml | 1 + app/code/Magento/Braintree/etc/di.xml | 2 +- .../js/view/payment/method-renderer/paypal.js | 18 ++- 5 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 app/code/Magento/Braintree/Gateway/Request/BillingAddressDataBuilder.php diff --git a/app/code/Magento/Braintree/Gateway/Request/BillingAddressDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/BillingAddressDataBuilder.php new file mode 100644 index 0000000000000..403c4d72fe358 --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Request/BillingAddressDataBuilder.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Gateway\Request; + +use Magento\Payment\Gateway\Request\BuilderInterface; +use Magento\Braintree\Gateway\SubjectReader; + +/** + * Class BillingAddressDataBuilder + */ +class BillingAddressDataBuilder implements BuilderInterface +{ + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * BillingAddress block name + */ + private const BILLING_ADDRESS = 'billing'; + + /** + * The customer’s company. 255 character maximum. + */ + private const COMPANY = 'company'; + + /** + * The first name value must be less than or equal to 255 characters. + */ + private const FIRST_NAME = 'firstName'; + + /** + * The last name value must be less than or equal to 255 characters. + */ + private const LAST_NAME = 'lastName'; + + /** + * The street address. Maximum 255 characters, and must contain at least 1 digit. + * Required when AVS rules are configured to require street address. + */ + private const STREET_ADDRESS = 'streetAddress'; + + /** + * The postal code. Postal code must be a string of 5 or 9 alphanumeric digits, + * optionally separated by a dash or a space. Spaces, hyphens, + * and all other special characters are ignored. + */ + private const POSTAL_CODE = 'postalCode'; + + /** + * The ISO 3166-1 alpha-2 country code specified in an address. + * The gateway only accepts specific alpha-2 values. + * + * @link https://developers.braintreepayments.com/reference/general/countries/php#list-of-countries + */ + private const COUNTRY_CODE = 'countryCodeAlpha2'; + + /** + * The extended address information—such as apartment or suite number. 255 character maximum. + */ + private const EXTENDED_ADDRESS = 'extendedAddress'; + + /** + * The locality/city. 255 character maximum. + */ + private const LOCALITY = 'locality'; + + /** + * The state or province. For PayPal addresses, the region must be a 2-letter abbreviation; + */ + private const REGION = 'region'; + + /** + * @param SubjectReader $subjectReader + */ + public function __construct(SubjectReader $subjectReader) + { + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject) + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + + $result = []; + $order = $paymentDO->getOrder(); + + $billingAddress = $order->getBillingAddress(); + if ($billingAddress) { + $result[self::BILLING_ADDRESS] = [ + self::REGION => $billingAddress->getRegionCode(), + self::POSTAL_CODE => $billingAddress->getPostcode(), + self::COUNTRY_CODE => $billingAddress->getCountryId(), + self::FIRST_NAME => $billingAddress->getFirstname(), + self::STREET_ADDRESS => $billingAddress->getStreetLine1(), + self::LAST_NAME => $billingAddress->getLastname(), + self::COMPANY => $billingAddress->getCompany(), + self::EXTENDED_ADDRESS => $billingAddress->getStreetLine2(), + self::LOCALITY => $billingAddress->getCity() + ]; + } + + return $result; + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ErrorCodeProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ErrorCodeProviderTest.php index cddb4852da0e3..605e9253fe2cc 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ErrorCodeProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ErrorCodeProviderTest.php @@ -74,9 +74,9 @@ public function getErrorCodeDataProvider(): array 'errors' => [], 'transaction' => [ 'status' => 'processor_declined', - 'processorResponseCode' => '1000' + 'processorResponseCode' => '2059' ], - 'expectedResult' => ['1000'] + 'expectedResult' => ['2059'] ], [ 'errors' => [ diff --git a/app/code/Magento/Braintree/etc/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml index 7155264b4e6ad..bffcc75705938 100644 --- a/app/code/Magento/Braintree/etc/braintree_error_mapping.xml +++ b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml @@ -21,6 +21,7 @@ <message code="81723" translate="true">Cardholder name is too long.</message> <message code="81736" translate="true">CVV verification failed.</message> <message code="cvv" translate="true">CVV verification failed.</message> + <message code="2059" translate="true">Address Verification Failed.</message> <message code="81737" translate="true">Postal code verification failed.</message> <message code="81750" translate="true">Credit card number is prohibited.</message> <message code="81801" translate="true">Addresses must have at least one field filled in.</message> diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index 6f8b7d1d6c368..150dea8e9fe44 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -381,7 +381,7 @@ <item name="customer" xsi:type="string">Magento\Braintree\Gateway\Request\CustomerDataBuilder</item> <item name="payment" xsi:type="string">Magento\Braintree\Gateway\Request\PaymentDataBuilder</item> <item name="channel" xsi:type="string">Magento\Braintree\Gateway\Request\ChannelDataBuilder</item> - <item name="address" xsi:type="string">Magento\Braintree\Gateway\Request\AddressDataBuilder</item> + <item name="address" xsi:type="string">Magento\Braintree\Gateway\Request\BillingAddressDataBuilder</item> <item name="dynamic_descriptor" xsi:type="string">Magento\Braintree\Gateway\Request\DescriptorDataBuilder</item> <item name="store" xsi:type="string">Magento\Braintree\Gateway\Request\StoreConfigBuilder</item> <item name="merchant_account" xsi:type="string">Magento\Braintree\Gateway\Request\MerchantAccountDataBuilder</item> diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index c46e65ffb8abd..17b233ec251b3 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -17,7 +17,8 @@ define([ 'Magento_Vault/js/view/payment/vault-enabler', 'Magento_Checkout/js/action/create-billing-address', 'Magento_Braintree/js/view/payment/kount', - 'mage/translate' + 'mage/translate', + 'Magento_Ui/js/model/messageList' ], function ( $, _, @@ -31,7 +32,8 @@ define([ VaultEnabler, createBillingAddress, kount, - $t + $t, + globalMessageList ) { 'use strict'; @@ -413,6 +415,18 @@ define([ */ onVaultPaymentTokenEnablerChange: function () { this.reInitPayPal(); + }, + + /** + * Show error message + * + * @param {String} errorMessage + * @private + */ + showError: function (errorMessage) { + globalMessageList.addErrorMessage({ + message: errorMessage + }); } }); }); From bf5bf0da626628943f69e08201e0798e7c275315 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Thu, 5 Sep 2019 09:14:15 +0300 Subject: [PATCH 637/841] MC-19669: Product images are not loaded when switching between variations --- .../Magento/Swatches/view/frontend/web/js/swatch-renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index f4b4be7657b8c..a5604adb0945b 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -754,7 +754,7 @@ define([ $widget.options.jsonConfig.optionPrices ]); - if (checkAdditionalData['update_product_preview_image'] === '1') { + if (parseInt(checkAdditionalData['update_product_preview_image']) === 1) { $widget._loadMedia(); } From 81b740b1d3f4bf88c59bfeb3f63d861951469c72 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Thu, 5 Sep 2019 10:03:45 +0300 Subject: [PATCH 638/841] MC-19716: Cannot change action settings of scheduled update for cart rule --- .../Adminhtml/Promo/Quote/NewActionHtml.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php index 45c0c5fab87ae..56c08864c90c4 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtml.php @@ -6,7 +6,9 @@ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Rule\Model\Condition\AbstractCondition; use Magento\SalesRule\Controller\Adminhtml\Promo\Quote; +use Magento\SalesRule\Model\Rule; /** * New action html action @@ -20,8 +22,10 @@ class NewActionHtml extends Quote implements HttpPostActionInterface */ public function execute() { - $id = $this->getRequest()->getParam('id'); - $formName = $this->getRequest()->getParam('form_namespace'); + $id = $this->getRequest() + ->getParam('id'); + $formName = $this->getRequest() + ->getParam('form_namespace'); $typeArr = explode('|', str_replace('-', '/', $this->getRequest()->getParam('type'))); $type = $typeArr[0]; @@ -32,7 +36,7 @@ public function execute() )->setType( $type )->setRule( - $this->_objectManager->create(\Magento\SalesRule\Model\Rule::class) + $this->_objectManager->create(Rule::class) )->setPrefix( 'actions' ); @@ -40,12 +44,14 @@ public function execute() $model->setAttribute($typeArr[1]); } - if ($model instanceof \Magento\Rule\Model\Condition\AbstractCondition) { + if ($model instanceof AbstractCondition) { $model->setJsFormObject($formName); + $model->setFormName($formName); $html = $model->asHtmlRecursive(); } else { $html = ''; } - $this->getResponse()->setBody($html); + $this->getResponse() + ->setBody($html); } } From af10d44c424610e2b87972f5d8d9f1a38183d0d8 Mon Sep 17 00:00:00 2001 From: Oskar Olaussen <oskar.olaussen@piimega.fi> Date: Thu, 5 Sep 2019 09:46:09 +0300 Subject: [PATCH 639/841] Fix for bug: If multiple children had the same sort order only one of them would be rendered --- app/code/Magento/Catalog/Block/Product/View/Details.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Details.php b/app/code/Magento/Catalog/Block/Product/View/Details.php index e76c5bf201334..21171f959f01c 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Details.php +++ b/app/code/Magento/Catalog/Block/Product/View/Details.php @@ -37,10 +37,11 @@ public function getGroupSortedChildNames(string $groupName, string $callback): a $alias = $layout->getElementAlias($childName); $sortOrder = (int)$this->getChildData($alias, 'sort_order') ?? 0; - $childNamesSortOrder[$sortOrder] = $childName; + $childNamesSortOrder[$childName] = $sortOrder; } - ksort($childNamesSortOrder, SORT_NUMERIC); + asort($childNamesSortOrder); + $childNamesSortOrder = array_keys($childNamesSortOrder); return $childNamesSortOrder; } From 6c4c8aa40723a84d4a65728b2c8bd0c4cd331acf Mon Sep 17 00:00:00 2001 From: Oleksandr Kravchuk <swnsma@gmail.com> Date: Thu, 5 Sep 2019 11:07:23 +0300 Subject: [PATCH 640/841] Update Details.php --- app/code/Magento/Catalog/Block/Product/View/Details.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Details.php b/app/code/Magento/Catalog/Block/Product/View/Details.php index 21171f959f01c..38925e9ae3cd7 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Details.php +++ b/app/code/Magento/Catalog/Block/Product/View/Details.php @@ -40,9 +40,8 @@ public function getGroupSortedChildNames(string $groupName, string $callback): a $childNamesSortOrder[$childName] = $sortOrder; } - asort($childNamesSortOrder); - $childNamesSortOrder = array_keys($childNamesSortOrder); + asort($childNamesSortOrder, SORT_NUMERIC); - return $childNamesSortOrder; + return array_keys($childNamesSortOrder); } } From 9999655f7dac4c9108529a87b0f26c6f8e11528a Mon Sep 17 00:00:00 2001 From: Aapo Kiiso <aapo@lamia.fi> Date: Thu, 5 Sep 2019 12:24:08 +0300 Subject: [PATCH 641/841] Mark Elasticsearch 6 configs as sensitive This is copied from the main Magento Elasticsearch module, where the sensitive configs have already been set for Elasticsearch 5. --- app/code/Magento/Elasticsearch6/etc/di.xml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml index 011dfa1019738..580c61ffc8cdb 100644 --- a/app/code/Magento/Elasticsearch6/etc/di.xml +++ b/app/code/Magento/Elasticsearch6/etc/di.xml @@ -202,4 +202,23 @@ </argument> </arguments> </virtualType> + + <type name="Magento\Config\Model\Config\TypePool"> + <arguments> + <argument name="sensitive" xsi:type="array"> + <item name="catalog/search/elasticsearch6_password" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_server_hostname" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_username" xsi:type="string">1</item> + </argument> + <argument name="environment" xsi:type="array"> + <item name="catalog/search/elasticsearch6_enable_auth" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_index_prefix" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_password" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_server_hostname" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_server_port" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_username" xsi:type="string">1</item> + <item name="catalog/search/elasticsearch6_server_timeout" xsi:type="string">1</item> + </argument> + </arguments> + </type> </config> From 85809dce6ae4684c7433580656e433336e293a28 Mon Sep 17 00:00:00 2001 From: "rostyslav.hymon" <rostyslav.hymon@transoftgroup.com> Date: Thu, 5 Sep 2019 12:40:05 +0300 Subject: [PATCH 642/841] MC-19669: Product images are not loaded when switching between variations --- .../Magento/Swatches/view/frontend/web/js/swatch-renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index a5604adb0945b..45c05c7a8db84 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -754,7 +754,7 @@ define([ $widget.options.jsonConfig.optionPrices ]); - if (parseInt(checkAdditionalData['update_product_preview_image']) === 1) { + if (parseInt(checkAdditionalData['update_product_preview_image'], 10) === 1) { $widget._loadMedia(); } From ea22c906a73d0482ae7c8e211c50036b77ddb187 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p.bystritsky@yandex.ru> Date: Wed, 4 Sep 2019 17:12:30 +0300 Subject: [PATCH 643/841] magento/magento2#24318: Static test fix. --- app/code/Magento/Review/Block/Adminhtml/Add.php | 1 + app/code/Magento/Review/view/adminhtml/web/js/rating.js | 1 + 2 files changed, 2 insertions(+) diff --git a/app/code/Magento/Review/Block/Adminhtml/Add.php b/app/code/Magento/Review/Block/Adminhtml/Add.php index bfa75b559a7ff..2edd76879d8dc 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Add.php +++ b/app/code/Magento/Review/Block/Adminhtml/Add.php @@ -17,6 +17,7 @@ class Add extends \Magento\Backend\Block\Widget\Form\Container * Initialize add review * * @return void + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ protected function _construct() { diff --git a/app/code/Magento/Review/view/adminhtml/web/js/rating.js b/app/code/Magento/Review/view/adminhtml/web/js/rating.js index 66067c6b50dcb..63e6eaa0a2c50 100644 --- a/app/code/Magento/Review/view/adminhtml/web/js/rating.js +++ b/app/code/Magento/Review/view/adminhtml/web/js/rating.js @@ -69,6 +69,7 @@ define([ */ removeRating: function () { var checkedInputs = this.element.find('input[type="radio"]'); + checkedInputs.nextAll('label').css('color', this.options.colorUnfilled).data('checked', false); } }); From ec9857daf41c9afe46d65012909c6dd18efe213c Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 5 Sep 2019 14:13:14 +0300 Subject: [PATCH 644/841] MC-19623: Redundant entity values --- .../Model/ResourceModel/Entity/Attribute.php | 30 ++++ .../Attribute/Entity/AttributeTest.php | 144 ++++++++++++++++++ .../Catalog/_files/dropdown_attribute.php | 1 - 3 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Attribute/Entity/AttributeTest.php diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 0e7a46125d872..8c4b119775e14 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -457,6 +457,7 @@ protected function _updateAttributeOption($object, $optionId, $option) if (!empty($option['delete'][$optionId])) { if ($intOptionId) { $connection->delete($table, ['option_id = ?' => $intOptionId]); + $this->clearSelectedOptionInEntities($object, $intOptionId); } return false; } @@ -475,6 +476,35 @@ protected function _updateAttributeOption($object, $optionId, $option) return $intOptionId; } + /** + * Clear selected option in entities + * + * @param EntityAttribute|AbstractModel $object + * @param int $optionId + * @return void + */ + private function clearSelectedOptionInEntities($object, $optionId) + { + $backendTable = $object->getBackendTable(); + $attributeId = $object->getAttributeId(); + if (!$backendTable || !$attributeId) { + return; + } + + $where = 'attribute_id = ' . $attributeId; + $update = []; + + if ($object->getBackendType() === 'varchar') { + $where.= " AND FIND_IN_SET('$optionId',value)"; + $update['value'] = new \Zend_Db_Expr("TRIM(BOTH ',' FROM REPLACE(CONCAT(',',value,','),',$optionId,',','))"); + } else { + $where.= ' AND value = ' . $optionId; + $update['value'] = null; + } + + $this->getConnection()->update($backendTable, $update, $where); + } + /** * Save option values records per store * diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Attribute/Entity/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Attribute/Entity/AttributeTest.php new file mode 100644 index 0000000000000..8ecf3da8e1aae --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Attribute/Entity/AttributeTest.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\ResourceModel\Attribute\Entity; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\Eav\Model\AttributeRepository; +use Magento\Eav\Model\ResourceModel\Entity\Attribute as AttributeResource; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CacheCleaner; + +/** + * Test Eav Resource Entity Attribute functionality + * + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Catalog/_files/dropdown_attribute.php + * @magentoDataFixture Magento/Catalog/_files/multiselect_attribute.php + * @magentoDataFixture Magento/Catalog/_files/product_without_options.php + */ +class AttributeTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var AttributeRepository + */ + protected $attributeRepository; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var AttributeResource + */ + private $model; + + /** + * @inheritdoc + */ + public function setUp() + { + CacheCleaner::cleanAll(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->attributeRepository = $this->objectManager->get(AttributeRepository::class); + $this->model = $this->objectManager->get(Attribute::class); + } + + /** + * Test to Clear selected option in entities after remove + */ + public function testClearSelectedOptionInEntities() + { + $dropdownAttribute = $this->loadAttribute('dropdown_attribute'); + $dropdownOption = array_keys($dropdownAttribute->getOptions())[1]; + + $multiplyAttribute = $this->loadAttribute('multiselect_attribute'); + $multiplyOptions = array_keys($multiplyAttribute->getOptions()); + $multiplySelectedOptions = implode(',', $multiplyOptions); + $multiplyOptionToRemove = $multiplyOptions[1]; + unset($multiplyOptions[1]); + $multiplyOptionsExpected = implode(',', $multiplyOptions); + + $product = $this->loadProduct('simple'); + $product->setData('dropdown_attribute', $dropdownOption); + $product->setData('multiselect_attribute', $multiplySelectedOptions); + $this->productRepository->save($product); + + $product = $this->loadProduct('simple'); + $this->assertEquals( + $dropdownOption, + $product->getData('dropdown_attribute'), + 'The dropdown attribute is not selected' + ); + $this->assertEquals( + $multiplySelectedOptions, + $product->getData('multiselect_attribute'), + 'The multiselect attribute is not selected' + ); + + $this->removeAttributeOption($dropdownAttribute, $dropdownOption); + $this->removeAttributeOption($multiplyAttribute, $multiplyOptionToRemove); + + $product = $this->loadProduct('simple'); + $this->assertEmpty($product->getData('dropdown_attribute')); + $this->assertEquals($multiplyOptionsExpected, $product->getData('multiselect_attribute')); + } + + /** + * Remove option from attribute + * + * @param Attribute $attribute + * @param int $optionId + */ + private function removeAttributeOption(Attribute $attribute, int $optionId): void + { + $removalMarker = [ + 'option' => [ + 'value' => [$optionId => []], + 'delete' => [$optionId => '1'], + ], + ]; + $attribute->addData($removalMarker); + $attribute->save($attribute); + } + + /** + * Load product by sku + * + * @param string $sku + * @return Product + */ + private function loadProduct(string $sku): Product + { + return $this->productRepository->get($sku, true, null, true); + } + + /** + * Load attrubute by code + * + * @param string $attributeCode + * @return Attribute + */ + private function loadAttribute(string $attributeCode): Attribute + { + /** @var Attribute $attribute */ + $attribute = $this->objectManager->create(Attribute::class); + $attribute->loadByCode(4, $attributeCode); + + return $attribute; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php index bb7e241d972e5..7077509d622d9 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute.php @@ -37,7 +37,6 @@ 'used_for_sort_by' => 0, 'frontend_label' => ['Drop-Down Attribute'], 'backend_type' => 'varchar', - 'backend_model' => \Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend::class, 'option' => [ 'value' => [ 'option_1' => ['Option 1'], From 686a71cc69a79a6d48ff2e92e556df3cf5234709 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Thu, 5 Sep 2019 14:17:18 +0300 Subject: [PATCH 645/841] MC-19716: Cannot change action settings of scheduled update for cart rule --- .../Promo/Quote/NewActionHtmlTest.php | 62 +++++++++++++++---- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php index c010ec3e92f74..82f1c53d8f161 100644 --- a/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewActionHtmlTest.php @@ -7,14 +7,30 @@ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\AbstractBackendController; /** * New action html test + * + * @magentoAppArea adminhtml */ class NewActionHtmlTest extends AbstractBackendController { + /** + * @var string + */ + protected $resource = 'Magento_SalesRule::quote'; + + /** + * @var string + */ + protected $uri = 'backend/sales_rule/promo_quote/newActionHtml'; + + /** + * @var string + */ + private $formName = 'test_form'; + /** * Test verifies that execute method has the proper data-form-part value in html response * @@ -22,20 +38,44 @@ class NewActionHtmlTest extends AbstractBackendController */ public function testExecute(): void { - $formName = 'test_form'; + $this->prepareRequest(); + $this->dispatch($this->uri); + $html = $this->getResponse() + ->getBody(); + $this->assertContains($this->formName, $html); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + parent::testAclNoAccess(); + } + + /** + * Prepare request + * + * @return void + */ + private function prepareRequest(): void + { $this->getRequest()->setParams( [ 'id' => 1, - 'form_namespace' => $formName, + 'form_namespace' => $this->formName, 'type' => 'Magento\SalesRule\Model\Rule\Condition\Product|quote_item_price', ] - ); - $objectManager = Bootstrap::getObjectManager(); - /** @var NewActionHtml $controller */ - $controller = $objectManager->create(NewActionHtml::class); - $controller->execute(); - $html = $this->getResponse() - ->getBody(); - $this->assertContains($formName, $html); + )->setMethod('POST'); } } From 232bbad232ac7c863ad0d77a4058599b24dcd228 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 5 Sep 2019 15:59:21 +0300 Subject: [PATCH 646/841] MC-19623: Redundant entity values --- app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 8c4b119775e14..eebe069835c97 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -496,7 +496,9 @@ private function clearSelectedOptionInEntities($object, $optionId) if ($object->getBackendType() === 'varchar') { $where.= " AND FIND_IN_SET('$optionId',value)"; - $update['value'] = new \Zend_Db_Expr("TRIM(BOTH ',' FROM REPLACE(CONCAT(',',value,','),',$optionId,',','))"); + $update['value'] = new \Zend_Db_Expr( + "TRIM(BOTH ',' FROM REPLACE(CONCAT(',',value,','),',$optionId,',','))" + ); } else { $where.= ' AND value = ' . $optionId; $update['value'] = null; From 1e6389c221fec44aca12090f79cca16fc7db9133 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 5 Sep 2019 16:21:34 +0300 Subject: [PATCH 647/841] MC-19777: Problems editing a specific product attribute --- .../Attribute/Edit/Options/Options.php | 71 +++++++++++++++---- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php index 72f4086c1c56b..7af7bf447c45a 100644 --- a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php +++ b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Options/Options.php @@ -4,16 +4,14 @@ * See COPYING.txt for license details. */ -/** - * Attribute add/edit form options tab - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Eav\Block\Adminhtml\Attribute\Edit\Options; use Magento\Store\Model\ResourceModel\Store\Collection; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; /** + * Attribute add/edit form options tab + * * @api * @since 100.0.2 */ @@ -61,6 +59,7 @@ public function __construct( /** * Is true only for system attributes which use source model + * * Option labels and position for such attributes are kept in source model and thus cannot be overridden * * @return bool @@ -96,12 +95,16 @@ public function getStoresSortedBySortOrder() { $stores = $this->getStores(); if (is_array($stores)) { - usort($stores, function ($storeA, $storeB) { - if ($storeA->getSortOrder() == $storeB->getSortOrder()) { - return $storeA->getId() < $storeB->getId() ? -1 : 1; + usort( + $stores, + function ($storeA, $storeB) { + if ($storeA->getSortOrder() == $storeB->getSortOrder()) { + return $storeA->getId() < $storeB->getId() ? -1 : 1; + } + + return ($storeA->getSortOrder() < $storeB->getSortOrder()) ? -1 : 1; } - return ($storeA->getSortOrder() < $storeB->getSortOrder()) ? -1 : 1; - }); + ); } return $stores; } @@ -130,12 +133,14 @@ public function getOptionValues() } /** - * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute + * Preparing values of attribute options + * + * @param AbstractAttribute $attribute * @param array|\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $optionCollection * @return array */ protected function _prepareOptionValues( - \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute, + AbstractAttribute $attribute, $optionCollection ) { $type = $attribute->getFrontendInput(); @@ -149,6 +154,41 @@ protected function _prepareOptionValues( $values = []; $isSystemAttribute = is_array($optionCollection); + if ($isSystemAttribute) { + $values = $this->getPreparedValues($optionCollection, $isSystemAttribute, $inputType, $defaultValues); + } else { + $optionCollection->setPageSize(200); + $pageCount = $optionCollection->getLastPageNumber(); + $currentPage = 1; + while ($currentPage <= $pageCount) { + $optionCollection->clear(); + $optionCollection->setCurPage($currentPage); + $values = array_merge( + $values, + $this->getPreparedValues($optionCollection, $isSystemAttribute, $inputType, $defaultValues) + ); + $currentPage++; + } + } + + return $values; + } + + /** + * Return prepared values of system or user defined attribute options + * + * @param array|\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $optionCollection + * @param bool $isSystemAttribute + * @param string $inputType + * @param array $defaultValues + */ + private function getPreparedValues( + $optionCollection, + bool $isSystemAttribute, + string $inputType, + array $defaultValues + ) { + $values = []; foreach ($optionCollection as $option) { $bunch = $isSystemAttribute ? $this->_prepareSystemAttributeOptionValues( $option, @@ -169,12 +209,13 @@ protected function _prepareOptionValues( /** * Retrieve option values collection + * * It is represented by an array in case of system attribute * - * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute + * @param AbstractAttribute $attribute * @return array|\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ - protected function _getOptionValuesCollection(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute) + protected function _getOptionValuesCollection(AbstractAttribute $attribute) { if ($this->canManageOptionDefaultOnly()) { $options = $this->_universalFactory->create( @@ -226,7 +267,7 @@ protected function _prepareSystemAttributeOptionValues($option, $inputType, $def foreach ($this->getStores() as $store) { $storeId = $store->getId(); $value['store' . $storeId] = $storeId == - \Magento\Store\Model\Store::DEFAULT_STORE_ID ? $valuePrefix . $this->escapeHtml($option['label']) : ''; + \Magento\Store\Model\Store::DEFAULT_STORE_ID ? $valuePrefix . $this->escapeHtml($option['label']) : ''; } return [$value]; From 9aa8b75d64c43eec5c9f56b65516052d52ccc6d5 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Thu, 5 Sep 2019 18:03:54 +0300 Subject: [PATCH 648/841] MC-19623: Redundant entity values --- .../Model/ResourceModel/Entity/Attribute.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index eebe069835c97..d05a7e1e2baa4 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -483,7 +483,7 @@ protected function _updateAttributeOption($object, $optionId, $option) * @param int $optionId * @return void */ - private function clearSelectedOptionInEntities($object, $optionId) + private function clearSelectedOptionInEntities(AbstractModel $object, int $optionId) { $backendTable = $object->getBackendTable(); $attributeId = $object->getAttributeId(); @@ -491,20 +491,24 @@ private function clearSelectedOptionInEntities($object, $optionId) return; } - $where = 'attribute_id = ' . $attributeId; + $connection = $this->getConnection(); + $where = $connection->quoteInto('attribute_id = ?', $attributeId); $update = []; if ($object->getBackendType() === 'varchar') { - $where.= " AND FIND_IN_SET('$optionId',value)"; - $update['value'] = new \Zend_Db_Expr( - "TRIM(BOTH ',' FROM REPLACE(CONCAT(',',value,','),',$optionId,',','))" + $where.= ' AND ' . $connection->prepareSqlCondition('value', ['finset' => $optionId]); + $concat = $connection->getConcatSql(["','", 'value', "','"]); + $expr = $connection->quoteInto( + "TRIM(BOTH ',' FROM REPLACE($concat,',?,',','))", + $optionId ); + $update['value'] = new \Zend_Db_Expr($expr); } else { - $where.= ' AND value = ' . $optionId; + $where.= $connection->quoteInto(' AND value = ?', $optionId); $update['value'] = null; } - $this->getConnection()->update($backendTable, $update, $where); + $connection->update($backendTable, $update, $where); } /** From 5a45cce48135291dcbf3bb7cb0c8201eecb12cb3 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 5 Sep 2019 12:01:40 -0500 Subject: [PATCH 649/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - return null instead of fields when there's no selected method --- .../Resolver/ShippingAddress/SelectedShippingMethod.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php index f2dacf6d007f3..5d2c4b81cf4af 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php @@ -64,14 +64,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value ], ]; } else { - $data = [ - 'carrier_code' => null, - 'method_code' => null, - 'carrier_title' => $carrierTitle, - 'method_title' => $methodTitle, - 'amount' => null, - 'base_amount' => null, - ]; + $data = null; } return $data; } From 2072344ad5196f3c66c6216bdf2a8b2c7f024251 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 5 Sep 2019 12:17:26 -0500 Subject: [PATCH 650/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Enable exact search by url_key and sku --- .../Plugin/Search/Request/ConfigReader.php | 55 ++++--------- app/code/Magento/CatalogGraphQl/etc/di.xml | 8 ++ .../CatalogGraphQl/etc/schema.graphqls | 2 +- .../Patch/Data/UpdateUrlKeySearchable.php | 79 +++++++++++++++++++ .../CatalogUrlRewriteGraphQl/etc/di.xml | 8 ++ .../etc/schema.graphqls | 4 + .../FieldMapper/Product/AttributeAdapter.php | 25 +++--- .../Product/FieldProvider/StaticField.php | 9 +-- .../SearchAdapter/Filter/Builder/Term.php | 43 +++++++++- 9 files changed, 172 insertions(+), 61 deletions(-) create mode 100644 app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeySearchable.php diff --git a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php index 85c18462aae49..992ab50467c72 100644 --- a/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php +++ b/app/code/Magento/CatalogGraphQl/Plugin/Search/Request/ConfigReader.php @@ -9,7 +9,6 @@ use Magento\CatalogSearch\Model\Search\RequestGenerator; use Magento\CatalogSearch\Model\Search\RequestGenerator\GeneratorResolver; use Magento\Eav\Model\Entity\Attribute; -use Magento\Framework\Search\EngineResolverInterface; use Magento\Framework\Search\Request\FilterInterface; use Magento\Framework\Search\Request\QueryInterface; use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; @@ -25,6 +24,9 @@ */ class ConfigReader { + /** Bucket name suffix */ + private const BUCKET_SUFFIX = '_bucket'; + /** * @var string */ @@ -46,26 +48,23 @@ class ConfigReader private $productAttributeCollectionFactory; /** - * @var EngineResolverInterface + * @var array */ - private $searchEngineResolver; - - /** Bucket name suffix */ - private const BUCKET_SUFFIX = '_bucket'; + private $exactMatchAttributes = []; /** * @param GeneratorResolver $generatorResolver * @param CollectionFactory $productAttributeCollectionFactory - * @param EngineResolverInterface $searchEngineResolver + * @param array $exactMatchAttributes */ public function __construct( GeneratorResolver $generatorResolver, CollectionFactory $productAttributeCollectionFactory, - EngineResolverInterface $searchEngineResolver + array $exactMatchAttributes = [] ) { $this->generatorResolver = $generatorResolver; $this->productAttributeCollectionFactory = $productAttributeCollectionFactory; - $this->searchEngineResolver = $searchEngineResolver; + $this->exactMatchAttributes = array_merge($this->exactMatchAttributes, $exactMatchAttributes); } /** @@ -142,12 +141,9 @@ private function generateRequest() case 'static': case 'text': case 'varchar': - if (in_array($attribute->getFrontendInput(), ['select', 'multiselect'])) { + if ($this->isExactMatchAttribute($attribute)) { $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName); $request['filters'][$filterName] = $this->generateTermFilter($filterName, $attribute); - } elseif ($attribute->getAttributeCode() === 'sku') { - $request['queries'][$queryName] = $this->generateFilterQuery($queryName, $filterName); - $request['filters'][$filterName] = $this->generateSkuTermFilter($filterName, $attribute); } else { $request['queries'][$queryName] = $this->generateMatchQuery($queryName, $attribute); } @@ -228,27 +224,6 @@ private function generateTermFilter(string $filterName, Attribute $attribute) ]; } - /** - * Generate term filter for sku field - * - * Sku needs to be treated specially to allow for exact match - * - * @param string $filterName - * @param Attribute $attribute - * @return array - */ - private function generateSkuTermFilter(string $filterName, Attribute $attribute) - { - $field = $this->isElasticSearch() ? 'sku.filter_sku' : 'sku'; - - return [ - 'type' => FilterInterface::TYPE_TERM, - 'name' => $filterName, - 'field' => $field, - 'value' => '$' . $attribute->getAttributeCode() . '$', - ]; - } - /** * Return array representation of query based on filter * @@ -292,16 +267,20 @@ private function generateMatchQuery(string $queryName, Attribute $attribute) } /** - * Check if the current search engine is elasticsearch + * Check if attribute's filter should use exact match * + * @param Attribute $attribute * @return bool */ - private function isElasticSearch() + private function isExactMatchAttribute(Attribute $attribute) { - $searchEngine = $this->searchEngineResolver->getCurrentSearchEngine(); - if (strpos($searchEngine, 'elasticsearch') === 0) { + if (in_array($attribute->getFrontendInput(), ['select', 'multiselect'])) { return true; } + if (in_array($attribute->getAttributeCode(), $this->exactMatchAttributes)) { + return true; + } + return false; } } diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml index 0fe30eb0503ea..485ae792193e3 100644 --- a/app/code/Magento/CatalogGraphQl/etc/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/di.xml @@ -58,6 +58,14 @@ </arguments> </type> + <type name="Magento\CatalogGraphQl\Plugin\Search\Request\ConfigReader"> + <arguments> + <argument name="exactMatchAttributes" xsi:type="array"> + <item name="sku" xsi:type="string">sku</item> + </argument> + </arguments> + </type> + <type name="Magento\Framework\Search\Request\Config\FilesystemReader"> <plugin name="productAttributesDynamicFields" type="Magento\CatalogGraphQl\Plugin\Search\Request\ConfigReader" /> </type> diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index a2ea18288a77a..b509865f0e82b 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -285,7 +285,7 @@ input ProductAttributeFilterInput @doc(description: "ProductAttributeFilterInput category_id: FilterEqualTypeInput @doc(description: "Filter product by category id") } -input ProductFilterInput @deprecated(reason: "Attributes used in this input are hardcoded and some of them are not searcheable. Use @ProductAttributeFilterInput instead") @doc(description: "ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { +input ProductFilterInput @doc(description: "ProductFilterInput is deprecated, use @ProductAttributeFilterInput instead. ProductFilterInput defines the filters to be used in the search. A filter contains at least one attribute, a comparison operator, and the value that is being searched for.") { name: FilterTypeInput @doc(description: "The product name. Customers use this name to identify the product.") sku: FilterTypeInput @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: FilterTypeInput @doc(description: "Detailed information about the product. The value can include simple HTML tags.") diff --git a/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeySearchable.php b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeySearchable.php new file mode 100644 index 0000000000000..75f88a8573069 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Setup/Patch/Data/UpdateUrlKeySearchable.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogUrlRewrite\Setup\Patch\Data; + +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Catalog\Setup\CategorySetup; +use Magento\Catalog\Setup\CategorySetupFactory; + +/** + * Update url_key to be searchable + */ +class UpdateUrlKeySearchable implements DataPatchInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * @var CategorySetupFactory + */ + private $categorySetupFactory; + + /** + * @param ModuleDataSetupInterface $moduleDataSetup + * @param CategorySetupFactory $categorySetupFactory + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup, + CategorySetupFactory $categorySetupFactory + ) { + $this->moduleDataSetup = $moduleDataSetup; + $this->categorySetupFactory = $categorySetupFactory; + } + + /** + * @inheritdoc + */ + public function apply() + { + /** @var CategorySetup $categorySetup */ + $categorySetup = $this->categorySetupFactory->create(['setup' => $this->moduleDataSetup]); + + $categorySetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'url_key', + 'is_searchable', + true + ); + + $categorySetup->updateAttribute( + \Magento\Catalog\Model\Category::ENTITY, + 'url_key', + 'is_searchable', + true + ); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return [CreateUrlAttributes::class]; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/di.xml b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/di.xml index 20e6b7e9c0053..e99f89477e807 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/di.xml +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/di.xml @@ -14,4 +14,12 @@ </argument> </arguments> </type> + + <type name="Magento\CatalogGraphQl\Plugin\Search\Request\ConfigReader"> + <arguments> + <argument name="exactMatchAttributes" xsi:type="array"> + <item name="url_key" xsi:type="string">url_key</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls index 89108e578d673..4453674de04dd 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls @@ -12,6 +12,10 @@ input ProductFilterInput { url_path: FilterTypeInput @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") } +input ProductAttributeFilterInput { + url_key: FilterEqualTypeInput @doc(description: "The part of the URL that identifies the product") +} + input ProductSortInput { url_key: SortEnum @doc(description: "The part of the URL that identifies the product") url_path: SortEnum @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php index c3952b494f2e9..41a50961ae4bc 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php @@ -115,6 +115,18 @@ public function isBooleanType(): bool && $this->getAttribute()->getBackendType() !== 'varchar'; } + /** + * Check if attribute is text type + * + * @return bool + */ + public function isTextType(): bool + { + return in_array($this->getAttribute()->getBackendType(), ['varchar', 'static'], true) + && in_array($this->getFrontendInput(), ['text'], true) + && $this->getAttribute()->getIsVisible(); + } + /** * Check if attribute has boolean type. * @@ -176,19 +188,6 @@ public function getFrontendInput() return $this->getAttribute()->getFrontendInput(); } - /** - * Check if product should always be filterable - * - * @return bool - */ - public function isAlwaysFilterable(): bool - { - // List of attributes which are required to be filterable - $alwaysFilterableAttributes = ['sku']; - - return in_array($this->getAttributeCode(), $alwaysFilterableAttributes, true); - } - /** * Get product attribute instance. * diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php index 466bf5d2d09eb..0f3020974d08a 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php @@ -130,12 +130,9 @@ public function getFields(array $context = []): array ]; } - if ($attributeAdapter->isAlwaysFilterable()) { - $filterFieldName = 'filter_' . $this->fieldNameResolver->getFieldName( - $attributeAdapter, - ['type' => FieldMapperInterface::TYPE_FILTER] - ); - $allAttributes[$fieldName]['fields'][$filterFieldName] = [ + if ($attributeAdapter->isTextType()) { + $keywordFieldName = FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD; + $allAttributes[$fieldName]['fields'][$keywordFieldName] = [ 'type' => $this->fieldTypeConverter->convert( FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD ) diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php index ed8cd049d2915..d88c7e53d813a 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Filter/Builder/Term.php @@ -5,10 +5,17 @@ */ namespace Magento\Elasticsearch\SearchAdapter\Filter\Builder; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\Request\Filter\Term as TermFilterRequest; use Magento\Framework\Search\Request\FilterInterface as RequestFilterInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; +use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ConverterInterface + as FieldTypeConverterInterface; +/** + * Term filter builder + */ class Term implements FilterInterface { /** @@ -16,26 +23,56 @@ class Term implements FilterInterface */ protected $fieldMapper; + /** + * @var AttributeProvider + */ + private $attributeAdapterProvider; + + /** + * @var array + * @see \Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\IntegerType::$integerTypeAttributes + */ + private $integerTypeAttributes = ['category_ids']; + /** * @param FieldMapperInterface $fieldMapper + * @param AttributeProvider $attributeAdapterProvider + * @param array $integerTypeAttributes */ - public function __construct(FieldMapperInterface $fieldMapper) - { + public function __construct( + FieldMapperInterface $fieldMapper, + AttributeProvider $attributeAdapterProvider = null, + array $integerTypeAttributes = [] + ) { $this->fieldMapper = $fieldMapper; + $this->attributeAdapterProvider = $attributeAdapterProvider + ?? ObjectManager::getInstance()->get(AttributeProvider::class); + $this->integerTypeAttributes = array_merge($this->integerTypeAttributes, $integerTypeAttributes); } /** + * Build term filter request + * * @param RequestFilterInterface|TermFilterRequest $filter * @return array */ public function buildFilter(RequestFilterInterface $filter) { $filterQuery = []; + + $attribute = $this->attributeAdapterProvider->getByAttributeCode($filter->getField()); + $fieldName = $this->fieldMapper->getFieldName($filter->getField()); + + if ($attribute->isTextType() && !in_array($attribute->getAttributeCode(), $this->integerTypeAttributes)) { + $suffix = FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD; + $fieldName .= '.' . $suffix; + } + if ($filter->getValue()) { $operator = is_array($filter->getValue()) ? 'terms' : 'term'; $filterQuery []= [ $operator => [ - $this->fieldMapper->getFieldName($filter->getField()) => $filter->getValue(), + $fieldName => $filter->getValue(), ], ]; } From c536350209d4e6c34ce8e93c374d7bcb451d0b76 Mon Sep 17 00:00:00 2001 From: Leland Clemmons <lclemmons@somethingdigital.com> Date: Thu, 5 Sep 2019 13:36:18 -0400 Subject: [PATCH 651/841] Fix lib-button-as-link not using color argument .lib-button-as-link(); was only using its @_link-color for disabled variants. It was also inefficiently emitting both the default .lib-link() hover colors, and the mixin's own provided @_link-color-hover variant. Let's bring both arguments into the actual .lib-link() call instead. --- lib/web/css/source/lib/_buttons.less | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/web/css/source/lib/_buttons.less b/lib/web/css/source/lib/_buttons.less index a92093b742902..a575442b77e55 100644 --- a/lib/web/css/source/lib/_buttons.less +++ b/lib/web/css/source/lib/_buttons.less @@ -200,15 +200,14 @@ .lib-css(line-height, @_line-height); .lib-css(margin, @_margin); .lib-css(padding, @_padding); - .lib-link(); + .lib-link( + @_link-color: @_link-color, + @_link-color-hover: @_link-color-hover + ); background: none; border: 0; display: inline; - &:hover { - .lib-css(color, @_link-color-hover); - } - &:hover, &:active, &:focus { From a52986d80245ee6dbce53317c05b74a598668fad Mon Sep 17 00:00:00 2001 From: Krissy Hiserote <khiserote@magento.com> Date: Thu, 5 Sep 2019 12:51:40 -0500 Subject: [PATCH 652/841] MC-16650: Product Attribute Type Price Not Displaying --- .../Test/Mftf/Section/StorefrontCategoryFilterSection.xml | 2 +- .../Magento/CatalogSearch/Model/Layer/Filter/Decimal.php | 5 ++++- .../CatalogSearch/Model/Search/RequestGenerator/Price.php | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml index fe3d3e298cbc9..faee605e77319 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryFilterSection.xml @@ -11,6 +11,6 @@ <section name="StorefrontCategoryFilterSection"> <element name="CategoryFilter" type="button" selector="//main//div[@class='filter-options']//div[contains(text(), 'Category')]"/> <element name="CategoryByName" type="button" selector="//main//div[@class='filter-options']//li[@class='item']//a[contains(text(), '{{var1}}')]" parameterized="true"/> - <element name="CustomPriceAttribute" type="button" selector="//div[contains(@class,'filter-options-title')]"/> + <element name="CustomPriceAttribute" type="button" selector="div.filter-options-title"/> </section> </sections> diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php index 596dc1bfc561d..3b0c4dfb6df2f 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Decimal.php @@ -14,6 +14,9 @@ */ class Decimal extends AbstractFilter { + /** Decimal delta for filter */ + private const DECIMAL_DELTA = 0.001; + /** * @var \Magento\Framework\Pricing\PriceCurrencyInterface */ @@ -75,7 +78,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) // When the range is 10-20 we only need to get products that are in the 10-19.99 range. $toValue = $to; if (!empty($toValue) && $from !== $toValue) { - $toValue -= 0.001; + $toValue -= self::DECIMAL_DELTA; } $this->getLayer() diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php index 7bf124844c618..949806d14f45a 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php +++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator/Price.php @@ -19,7 +19,7 @@ class Price implements GeneratorInterface /** * @inheritdoc */ - public function getFilterData(Attribute $attribute, $filterName) + public function getFilterData(Attribute $attribute, $filterName): array { return [ 'type' => FilterInterface::TYPE_RANGE, @@ -33,7 +33,7 @@ public function getFilterData(Attribute $attribute, $filterName) /** * @inheritdoc */ - public function getAggregationData(Attribute $attribute, $bucketName) + public function getAggregationData(Attribute $attribute, $bucketName): array { return [ 'type' => BucketInterface::TYPE_DYNAMIC, From 7211d50998f786dce0628346439649ac8d196c46 Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk <odubovyk@magento.com> Date: Thu, 5 Sep 2019 14:40:08 -0500 Subject: [PATCH 653/841] MC-19440: Bug generating a country list in Admin when Country is restricted - fixed --- .../view/adminhtml/ui_component/customer_address_form.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml index 692cb2ecb964d..3af0172b3fca8 100644 --- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml +++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_address_form.xml @@ -191,13 +191,6 @@ </validation> <dataType>text</dataType> </settings> - <formElements> - <select> - <settings> - <options class="Magento\Directory\Model\ResourceModel\Country\Collection"/> - </settings> - </select> - </formElements> </field> <field name="region_id" component="Magento_Customer/js/form/element/region" formElement="select"> <settings> From 2859ae67f974c079e5bca4fa65684881ea6e8630 Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Thu, 5 Sep 2019 14:52:19 -0500 Subject: [PATCH 654/841] Adding Global root category --- .../Model/Resolver/Products/DataProvider/CategoryTree.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index fc5a563c82b4e..9c7517908bf2e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -140,6 +140,9 @@ private function joinAttributesRecursively(Collection $collection, FieldNode $fi if ($node->kind === 'InlineFragment') { continue; } + if ($node->kind === 'FragmentSpread') { + continue; + } $this->joinAttributesRecursively($collection, $node); } From 02aa0fea17b409001bc8dce3d35c7add0c7888db Mon Sep 17 00:00:00 2001 From: Rus0 <andonid88@gmail.com> Date: Thu, 5 Sep 2019 14:56:36 -0500 Subject: [PATCH 655/841] messed up commit --- .../Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php | 5 ++++- .../Model/Resolver/Products/DataProvider/CategoryTree.php | 3 --- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php index aefe7b97b8a3f..4284aed610848 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; +use Magento\Catalog\Model\Category; use Magento\CatalogGraphQl\Model\Resolver\Category\CheckCategoryIsActive; use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\ExtractDataFromCategoryTree; use Magento\Framework\GraphQl\Config\Element\Field; @@ -66,8 +67,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $rootCategoryId = isset($args['id']) ? (int)$args['id'] : (int)$context->getExtensionAttributes()->getStore()->getRootCategoryId(); - $this->checkCategoryIsActive->execute($rootCategoryId); + if ($rootCategoryId !== Category::TREE_ROOT_ID) { + $this->checkCategoryIsActive->execute($rootCategoryId); + } $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId); if (empty($categoriesTree) || ($categoriesTree->count() == 0)) { diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index 9c7517908bf2e..fc5a563c82b4e 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -140,9 +140,6 @@ private function joinAttributesRecursively(Collection $collection, FieldNode $fi if ($node->kind === 'InlineFragment') { continue; } - if ($node->kind === 'FragmentSpread') { - continue; - } $this->joinAttributesRecursively($collection, $node); } From d54281abd23bb854c28b78835e38c815b69a085c Mon Sep 17 00:00:00 2001 From: "Kristof Ringleff, Fooman" <kristof@fooman.co.nz> Date: Wed, 4 Sep 2019 16:01:25 +1200 Subject: [PATCH 656/841] Add resolver to determine if quote is virtual --- .../Model/Resolver/CartIsVirtual.php | 34 +++++ .../Magento/QuoteGraphQl/etc/schema.graphqls | 1 + .../Quote/Customer/GetCartIsVirtualTest.php | 119 ++++++++++++++++++ .../Quote/Guest/GetCartIsVirtualTest.php | 97 ++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 app/code/Magento/QuoteGraphQl/Model/Resolver/CartIsVirtual.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartIsVirtualTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartIsVirtualTest.php diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartIsVirtual.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartIsVirtual.php new file mode 100644 index 0000000000000..3aec0a8365da8 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartIsVirtual.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Quote\Model\Quote; + +/** + * @inheritdoc + */ +class CartIsVirtual implements ResolverInterface +{ + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" value should be specified')); + } + /** @var Quote $cart */ + $cart = $value['model']; + + return (bool)$cart->getIsVirtual(); + } +} diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index c458b5e9dc05d..e05d3f8858845 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -198,6 +198,7 @@ type Cart { selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod") prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices") total_quantity: Float! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTotalQuantity") + is_virtual: Boolean! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartIsVirtual") } interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartIsVirtualTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartIsVirtualTest.php new file mode 100644 index 0000000000000..cf72435a123bf --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartIsVirtualTest.php @@ -0,0 +1,119 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Customer; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for getting is_virtual from cart + */ +class GetCartIsVirtualTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testGetCartIsNotVirtual() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('is_virtual', $response['cart']); + $this->assertFalse($response['cart']['is_virtual']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php + */ + public function testGetMixedCartIsNotVirtual() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('is_virtual', $response['cart']); + $this->assertFalse($response['cart']['is_virtual']); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php + */ + public function testGetCartIsVirtual() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('is_virtual', $response['cart']); + $this->assertTrue($response['cart']['is_virtual']); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id:"$maskedQuoteId") { + is_virtual + } +} +QUERY; + } + + /** + * @param string $username + * @param string $password + * @return array + */ + private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + return $headerMap; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartIsVirtualTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartIsVirtualTest.php new file mode 100644 index 0000000000000..79fe2273184b2 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartIsVirtualTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote\Guest; + +use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for getting is_virtual from cart + */ +class GetCartIsVirtualTest extends GraphQlAbstract +{ + /** + * @var GetMaskedQuoteIdByReservedOrderId + */ + private $getMaskedQuoteIdByReservedOrderId; + + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testGetCartIsNotVirtual() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('is_virtual', $response['cart']); + $this->assertFalse($response['cart']['is_virtual']); + } + + /** + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php + */ + public function testGetMixedCartIsNotVirtual() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('is_virtual', $response['cart']); + $this->assertFalse($response['cart']['is_virtual']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php + */ + public function testGetCartIsVirtual() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = $this->getQuery($maskedQuoteId); + $response = $this->graphQlQuery($query); + + $this->assertArrayHasKey('cart', $response); + $this->assertArrayHasKey('is_virtual', $response['cart']); + $this->assertTrue($response['cart']['is_virtual']); + } + + /** + * @param string $maskedQuoteId + * @return string + */ + private function getQuery(string $maskedQuoteId): string + { + return <<<QUERY +{ + cart(cart_id:"$maskedQuoteId") { + is_virtual + } +} +QUERY; + } +} From eb3b3d0b29fdda854a263f68a19e353153a315a1 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 5 Sep 2019 16:43:28 -0500 Subject: [PATCH 657/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Update performance tests --- setup/performance-toolkit/benchmark.jmx | 65 +++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index d53d59c4f7de8..8ffec6aff0422 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -38390,7 +38390,7 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n filter: {\n price: {gt: \"10\"}\n or: {\n sku:{like:\"%Product%\"}\n name:{like:\"%Configurable Product%\"}\n }\n }\n pageSize: 20\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n filter: {\n price: {from: \"5\"}\n name:{match:\"Product\"}\n }\n pageSize: 20\n currentPage: 1\n sort: {\n price: ASC\n name:DESC\n }\n ) {\n total_count\n items {\n attribute_set_id\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n \t... on PhysicalProductInterface {\n \tweight\n \t}\n }\n page_info {\n page_size\n current_page\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38592,7 +38592,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"configurable\"\n filter: {name: {like: \"Configurable Product%\"} }\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"configurable\"\n filter: {name: {match: \"Configurable Product\"} }\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38631,7 +38631,7 @@ if (totalCount == null) { </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> <hashTree/> <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> - <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count_fulltext_filter"); + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count_fulltext_filter"); if (totalCount == null) { Failure = true; @@ -38660,7 +38660,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:${graphql_search_products_query_total_pages_fulltext_filter}\n search: \"configurable\"\n filter: {name: {like: \"Configurable Product%\"} }\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:${graphql_search_products_query_total_pages_fulltext_filter}\n search: \"configurable\"\n filter: {name: {match: \"Configurable Product\"} }\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38834,6 +38834,63 @@ if (totalCount == null) { <hashTree/> </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query products with full text search and aggregations" enabled="true"> + <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> + <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> + <collectionProp name="Arguments.arguments"> + <elementProp name="" elementType="HTTPArgument"> + <boolProp name="HTTPArgument.always_encode">false</boolProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"Option 1\") {\n aggregations{\n attribute_code\n count\n label\n options{\n count\n label\n value\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.metadata">=</stringProp> + </elementProp> + </collectionProp> + </elementProp> + <stringProp name="HTTPSampler.domain"/> + <stringProp name="HTTPSampler.port"/> + <stringProp name="HTTPSampler.connect_timeout"/> + <stringProp name="HTTPSampler.response_timeout"/> + <stringProp name="HTTPSampler.protocol">${request_protocol}</stringProp> + <stringProp name="HTTPSampler.contentEncoding"/> + <stringProp name="HTTPSampler.path">${base_path}graphql</stringProp> + <stringProp name="HTTPSampler.method">POST</stringProp> + <boolProp name="HTTPSampler.follow_redirects">true</boolProp> + <boolProp name="HTTPSampler.auto_redirects">false</boolProp> + <boolProp name="HTTPSampler.use_keepalive">true</boolProp> + <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp> + <boolProp name="HTTPSampler.monitor">false</boolProp> + <stringProp name="HTTPSampler.embedded_url_re"/> + <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/graphql/query_multiple_products_with_extensible_data_objects_using_full_text_and_aggregations.jmx</stringProp> + </HTTPSamplerProxy> + <hashTree> + <com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor guiclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.gui.JSONPathExtractorGui" testclass="com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor" testname="Extract total_count" enabled="true"> + <stringProp name="VAR">graphql_search_products_query_total_count</stringProp> + <stringProp name="JSONPATH">$.data.products.total_count</stringProp> + <stringProp name="DEFAULT"/> + <stringProp name="VARIABLE"/> + <stringProp name="SUBJECT">BODY</stringProp> + </com.atlantbh.jmeter.plugins.jsonutils.jsonpathextractor.JSONPathExtractor> + <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Assert total count > 0" enabled="true"> + <stringProp name="BeanShellAssertion.query">String totalCount=vars.get("graphql_search_products_query_total_count"); + if (totalCount == null) { + Failure = true; + FailureMessage = "Not Expected \"totalCount\" to be null"; + } else { + if (Integer.parseInt(totalCount) < 1) { + Failure = true; + FailureMessage = "Expected \"totalCount\" to be greater than zero, Actual: " + totalCount; + } else { + Failure = false; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> + </hashTree> + <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Query bundle product" enabled="true"> <boolProp name="HTTPSampler.postBodyRaw">true</boolProp> <elementProp name="HTTPsampler.Arguments" elementType="Arguments"> From 657132a9ee967a074d691674ec3eb47ca9b8607a Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Thu, 5 Sep 2019 17:48:01 -0500 Subject: [PATCH 658/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix tests --- .../GraphQl/Catalog/ProductSearchTest.php | 635 +++++++++++++----- ...th_custom_attribute_layered_navigation.php | 14 +- .../_files/dropdown_attribute_rollback.php | 18 + .../_files/products_for_relevance_sorting.php | 75 ++- ...roducts_for_relevance_sorting_rollback.php | 36 + ...th_layered_navigation_custom_attribute.php | 2 +- 6 files changed, 573 insertions(+), 207 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 8d8969737b7f8..cef036a31e6b7 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -13,8 +13,11 @@ use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Category; use Magento\Catalog\Model\CategoryLinkManagement; +use Magento\Eav\Model\Config; use Magento\Framework\Config\Data; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Indexer\IndexerInterface; +use Magento\Indexer\Model\Indexer; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; use Magento\Catalog\Model\Product; @@ -32,20 +35,25 @@ class ProductSearchTest extends GraphQlAbstract { /** - * Verify that layered navigation filters are returned for product query + * Verify that layered navigation filters and aggregations are correct for product query * + * Filter products by an array of skus * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testFilterLn() { - CacheCleaner::cleanAll(); + /** @var \Magento\Eav\Model\Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(Config::class); + $attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + /** @var \Magento\Eav\Api\Data\AttributeOptionInterface[] $options */ + $options = $attribute->getOptions(); $query = <<<QUERY { products ( filter: { sku: { - like:"simple%" + in:["simple1", "simple2"] } } pageSize: 4 @@ -77,9 +85,6 @@ public function testFilterLn() } } QUERY; - /** - * @var ProductRepositoryInterface $productRepository - */ $response = $this->graphQlQuery($query); $this->assertArrayHasKey( @@ -105,69 +110,111 @@ public function testLayeredNavigationWithConfigurableChildrenOutOfStock() { CacheCleaner::cleanAll(); $attributeCode = 'test_configurable'; + /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(Config::class); $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); /** @var AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); array_shift($options); $firstOption = $options[0]->getValue(); $secondOption = $options[1]->getValue(); - $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); + $query = $this->getQueryProductsWithArrayOfCustomAttributes($attributeCode, $firstOption, $secondOption); + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); $response = $this->graphQlQuery($query); - // Out of two children, only one child product of 1st Configurable product with option1 is OOS - $this->assertEquals(1, $response['products']['total_count']); + $this->assertEquals(2, $response['products']['total_count']); + $this->assertNotEmpty($response['products']['aggregations']); + $this->assertNotEmpty($response['products']['filters'],'Filters is empty'); + $this->assertCount(2, $response['products']['aggregations'], 'Aggregation count does not match'); + //$this->assertResponseFields($response['products']['aggregations']) // Custom attribute filter layer data $this->assertResponseFields( - $response['products']['filters'][1], + $response['products']['aggregations'][1], [ - 'name' => $attribute->getDefaultFrontendLabel(), - 'request_var'=> $attribute->getAttributeCode(), - 'filter_items_count'=> 2, - 'filter_items' => [ + 'attribute_code' => $attribute->getAttributeCode(), + 'label'=> $attribute->getDefaultFrontendLabel(), + 'count'=> 2, + 'options' => [ [ 'label' => 'Option 1', - 'items_count' => 1, - 'value_string' => $firstOption, - '__typename' =>'LayerFilterItem' + 'value' => $firstOption, + 'count' =>'2' ], [ 'label' => 'Option 2', - 'items_count' => 1, - 'value_string' => $secondOption, - '__typename' =>'LayerFilterItem' + 'value' => $secondOption, + 'count' =>'2' ] ], ] ); + } - /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ - $productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $outOfStockChildProduct = $productRepository->get('simple_30'); - // All child variations with this attribute are now set to Out of Stock - $outOfStockChildProduct->setStockData( - ['use_config_manage_stock' => 1, - 'qty' => 0, - 'is_qty_decimal' => 0, - 'is_in_stock' => 0] - ); - $productRepository->save($outOfStockChildProduct); - $query = $this->getQueryProductsWithCustomAttribute($attributeCode, $firstOption); - $response = $this->graphQlQuery($query); - $this->assertEquals(0, $response['products']['total_count']); - $this->assertEmpty($response['products']['items']); - $this->assertEmpty($response['products']['filters']); + /** + * + * @return string + */ + private function getQueryProductsWithArrayOfCustomAttributes($attributeCode, $firstOption, $secondOption) : string + { + return <<<QUERY +{ + products(filter:{ + $attributeCode: {in:["{$firstOption}", "{$secondOption}"]} + } + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + } + aggregations{ + attribute_code + count + label + options{ + label + value + count + } + } + + } +} +QUERY; } /** - * Filter products using custom attribute of input type select(dropdown) and filterTypeInput eq + * Filter products by custom attribute of dropdown type and filterTypeInput eq * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testAdvancedSearchByOneCustomAttribute() + public function testFilterProductsByDropDownCustomAttribute() { CacheCleaner::cleanAll(); $attributeCode = 'second_test_configurable'; @@ -203,7 +250,18 @@ public function testAdvancedSearchByOneCustomAttribute() __typename } - } + } + aggregations{ + attribute_code + count + label + options + { + label + count + value + } + } } } @@ -215,11 +273,15 @@ public function testAdvancedSearchByOneCustomAttribute() $product2 = $productRepository->get('12345'); $product3 = $productRepository->get('simple-4'); $filteredProducts = [$product1, $product2, $product3 ]; + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); $response = $this->graphQlQuery($query); $this->assertEquals(3, $response['products']['total_count']); $this->assertTrue(count($response['products']['filters']) > 0, 'Product filters is not empty'); $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); - // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall + //phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall for ($itemIndex = 0; $itemIndex < count($filteredProducts); $itemIndex++) { $this->assertNotEmpty($productItemsInResponse[$itemIndex]); //validate that correct products are returned @@ -234,33 +296,35 @@ public function testAdvancedSearchByOneCustomAttribute() /** @var \Magento\Eav\Model\Config $eavConfig */ $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); $attribute = $eavConfig->getAttribute('catalog_product', 'second_test_configurable'); - - // Validate custom attribute filter layer data + // Validate custom attribute filter layer data from aggregations $this->assertResponseFields( - $response['products']['filters'][2], + $response['products']['aggregations'][2], [ - 'name' => $attribute->getDefaultFrontendLabel(), - 'request_var'=> $attribute->getAttributeCode(), - 'filter_items_count'=> 1, - 'filter_items' => [ + 'attribute_code' => $attribute->getAttributeCode(), + 'count'=> 1, + 'label'=> $attribute->getDefaultFrontendLabel(), + 'options' => [ [ 'label' => 'Option 3', - 'items_count' => 3, - 'value_string' => $optionValue, - '__typename' =>'LayerFilterItem' + 'count' => 3, + 'value' => $optionValue ], ], ] ); } /** - * Filter products using custom attribute of input type select(dropdown) and filterTypeInput eq + * Filter products using an array of multi select custom attributes * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_with_multiselect_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testFilterProductsByMultiSelectCustomAttribute() + public function testFilterProductsByMultiSelectCustomAttributes() { + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); CacheCleaner::cleanAll(); $attributeCode = 'multiselect_attribute'; /** @var \Magento\Eav\Model\Config $eavConfig */ @@ -304,8 +368,18 @@ public function testFilterProductsByMultiSelectCustomAttribute() value_string __typename } - - } + } + aggregations{ + attribute_code + count + label + options + { + label + value + + } + } } } @@ -314,6 +388,7 @@ public function testFilterProductsByMultiSelectCustomAttribute() $response = $this->graphQlQuery($query); $this->assertEquals(3, $response['products']['total_count']); $this->assertNotEmpty($response['products']['filters']); + $this->assertNotEmpty($response['products']['aggregations']); } /** @@ -335,22 +410,27 @@ private function getDefaultAttributeOptionValue(string $attributeCode) : string } /** - * Full text search for Product and then filter the results by custom attribute + * Full text search for Products and then filter the results by custom attribute ( sort is by defaulty by relevance) * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testFullTextSearchForProductAndFilterByCustomAttribute() + public function testSearchAndFilterByCustomAttribute() { + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); CacheCleaner::cleanAll(); - $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); + $attribute_code = 'second_test_configurable'; + $optionValue = $this->getDefaultAttributeOptionValue($attribute_code); $query = <<<QUERY { products(search:"Simple", filter:{ - second_test_configurable: {eq: "{$optionValue}"} - }, + $attribute_code: {in:["{$optionValue}"]} + } pageSize: 3 currentPage: 1 ) @@ -377,7 +457,19 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() __typename } - } + } + aggregations + { + attribute_code + count + label + options + { + count + label + value + } + } } @@ -386,21 +478,20 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() $response = $this->graphQlQuery($query); //Verify total count of the products returned $this->assertEquals(3, $response['products']['total_count']); + $this->assertArrayHasKey('filters', $response['products']); + $this->assertCount(3, $response['products']['aggregations']); $expectedFilterLayers = [ - ['name' => 'Price', - 'request_var'=> 'price' - ], ['name' => 'Category', - 'request_var'=> 'category_id' + 'request_var'=> 'cat' ], ['name' => 'Second Test Configurable', - 'request_var'=> 'second_test_configurable' - ], + 'request_var'=> 'second_test_configurable' + ] ]; $layers = array_map(null, $expectedFilterLayers, $response['products']['filters']); - //Verify all the three layers : Price, Category and Custom attribute layers are created + //Verify all the three layers from filters : Price, Category and Custom attribute layers foreach ($layers as $layerIndex => $layerFilterData) { $this->assertNotEmpty($layerFilterData); $this->assertEquals( @@ -415,39 +506,74 @@ public function testFullTextSearchForProductAndFilterByCustomAttribute() ); } - // Validate the price filter layer data from the response + // Validate the price layer of aggregations from the response $this->assertResponseFields( - $response['products']['filters'][0], + $response['products']['aggregations'][0], [ - 'name' => 'Price', - 'request_var'=> 'price', - 'filter_items_count'=> 2, - 'filter_items' => [ + 'attribute_code' => 'price', + 'count'=> 2, + 'label'=> 'Price', + 'options' => [ [ + 'count' => 2, 'label' => '10-20', - 'items_count' => 2, - 'value_string' => '10_20', - '__typename' =>'LayerFilterItem' + 'value' => '10_20', + ], - [ - 'label' => '40-*', - 'items_count' => 1, - 'value_string' => '40_*', - '__typename' =>'LayerFilterItem' - ], + [ + 'count' => 1, + 'label' => '40-*', + 'value' => '40_*', + + ], ], ] ); + // Validate the custom attribute layer of aggregations from the response + $this->assertResponseFields( + $response['products']['aggregations'][2], + [ + 'attribute_code' => $attribute_code, + 'count'=> 1, + 'label'=> 'Second Test Configurable', + 'options' => [ + [ + 'count' => 3, + 'label' => 'Option 3', + 'value' => $optionValue, + + ] + + ], + ] + ); + // 7 categories including the subcategories to which the items belong to , are returned + $this->assertCount(7, $response['products']['aggregations'][1]['options']); + unset($response['products']['aggregations'][1]['options']); + $this->assertResponseFields( + $response['products']['aggregations'][1], + [ + 'attribute_code' => 'category_id', + 'count'=> 7, + 'label'=> 'Category' + ] + ); } /** - * Filter by single category and custom attribute + * Filter by category and custom attribute * * @magentoApiDataFixture Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testFilterByCategoryIdAndCustomAttribute() { + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); + CacheCleaner::cleanAll(); + $categoryId = 13; $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); $query = <<<QUERY @@ -481,52 +607,99 @@ public function testFilterByCategoryIdAndCustomAttribute() value_string __typename } - - } - - } + } + aggregations + { + attribute_code + count + label + options + { + count + label + value + } + } + } } QUERY; $response = $this->graphQlQuery($query); $this->assertEquals(2, $response['products']['total_count']); - $actualCategoryFilterItems = $response['products']['filters'][1]['filter_items']; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $product1 = $productRepository->get('simple-4'); + $product2 = $productRepository->get('simple'); + $filteredProducts = [$product1, $product2]; + $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); + //phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall + for ($itemIndex = 0; $itemIndex < count($filteredProducts); $itemIndex++) { + $this->assertNotEmpty($productItemsInResponse[$itemIndex]); + //validate that correct products are returned + $this->assertResponseFields( + $productItemsInResponse[$itemIndex][0], + [ 'name' => $filteredProducts[$itemIndex]->getName(), + 'sku' => $filteredProducts[$itemIndex]->getSku() + ] + ); + } + $this->assertNotEmpty($response['products']['filters'],'filters is empty'); + $this->assertNotEmpty($response['products']['aggregations'], 'Aggregations should not be empty'); + $this->assertCount(3, $response['products']['aggregations']); + + $actualCategoriesFromResponse = $response['products']['aggregations'][1]['options']; + //Validate the number of categories/sub-categories that contain the products with the custom attribute - $this->assertCount(6, $actualCategoryFilterItems); + $this->assertCount(6, $actualCategoriesFromResponse); - $expectedCategoryFilterItems = + $expectedCategoryInAggregrations = [ - [ 'label' => 'Category 1', - 'items_count'=> 2 + [ + 'count' => 2, + 'label' => 'Category 1', + 'value'=> '3' ], - [ 'label' => 'Category 1.1', - 'items_count'=> 1 + [ + 'count'=> 1, + 'label' => 'Category 1.1', + 'value'=> '4' + ], - [ 'label' => 'Movable Position 2', - 'items_count'=> 1 + [ + 'count'=> 1, + 'label' => 'Movable Position 2', + 'value'=> '10' + ], - [ 'label' => 'Movable Position 3', - 'items_count'=> 1 + [ + 'count'=> 1, + 'label' => 'Movable Position 3', + 'value'=> '11' ], - [ 'label' => 'Category 12', - 'items_count'=> 1 + [ + 'count'=> 1, + 'label' => 'Category 12', + 'value'=> '12' + ], - [ 'label' => 'Category 1.2', - 'items_count'=> 2 + [ + 'count'=> 2, + 'label' => 'Category 1.2', + 'value'=> '13' ], ]; - $categoryFilterItems = array_map(null, $expectedCategoryFilterItems, $actualCategoryFilterItems); + $categoryInAggregations = array_map(null, $expectedCategoryInAggregrations, $actualCategoriesFromResponse); //Validate the categories and sub-categories data in the filter layer - foreach ($categoryFilterItems as $index => $categoryFilterData) { - $this->assertNotEmpty($categoryFilterData); + foreach ($categoryInAggregations as $index => $categoryAggregationsData) { + $this->assertNotEmpty($categoryAggregationsData); $this->assertEquals( - $categoryFilterItems[$index][0]['label'], - $actualCategoryFilterItems[$index]['label'], + $categoryInAggregations[$index][0]['label'], + $actualCategoriesFromResponse[$index]['label'], 'Category is incorrect' ); $this->assertEquals( - $categoryFilterItems[$index][0]['items_count'], - $actualCategoryFilterItems[$index]['items_count'], + $categoryInAggregations[$index][0]['count'], + $actualCategoriesFromResponse[$index]['count'], 'Products count in the category is incorrect' ); } @@ -567,8 +740,18 @@ private function getQueryProductsWithCustomAttribute($attributeCode, $optionValu value_string __typename } + - } + } + aggregations{ + attribute_code + count + label + options{ + label + value + } + } } } @@ -589,32 +772,15 @@ private function getExpectedFiltersDataSet() $options = $attribute->getOptions(); // Fetching option ID is required for continuous debug as of autoincrement IDs. return [ - [ - 'name' => 'Price', - 'filter_items_count' => 2, - 'request_var' => 'price', - 'filter_items' => [ - [ - 'label' => '*-10', - 'value_string' => '*_10', - 'items_count' => 1, - ], - [ - 'label' => '10-*', - 'value_string' => '10_*', - 'items_count' => 1, - ], - ], - ], [ 'name' => 'Category', 'filter_items_count' => 1, - 'request_var' => 'category_id', + 'request_var' => 'cat', 'filter_items' => [ [ 'label' => 'Category 1', 'value_string' => '333', - 'items_count' => 2, + 'items_count' => 3, ], ], ], @@ -629,7 +795,24 @@ private function getExpectedFiltersDataSet() 'items_count' => 1, ], ], - ] + ], + [ + 'name' => 'Price', + 'filter_items_count' => 2, + 'request_var' => 'price', + 'filter_items' => [ + [ + 'label' => '<span class="price">$0.00</span> - <span class="price">$9.99</span>', + 'value_string' => '-10', + 'items_count' => 1, + ], + [ + 'label' => '<span class="price">$10.00</span> and above', + 'value_string' => '10-', + 'items_count' => 1, + ], + ], + ], ]; } @@ -643,8 +826,8 @@ private function getExpectedFiltersDataSet() private function assertFilters($response, $expectedFilters, $message = '') { $this->assertArrayHasKey('filters', $response['products'], 'Product has filters'); - $this->assertTrue(is_array(($response['products']['filters'])), 'Product filters is array'); - $this->assertTrue(count($response['products']['filters']) > 0, 'Product filters is not empty'); + $this->assertTrue(is_array(($response['products']['filters'])), 'Product filters is not array'); + $this->assertTrue(count($response['products']['filters']) > 0, 'Product filters is empty'); foreach ($expectedFilters as $expectedFilter) { $found = false; foreach ($response['products']['filters'] as $responseFilter) { @@ -661,7 +844,7 @@ private function assertFilters($response, $expectedFilters, $message = '') } /** - * Verify that items between the price range of 5 and 50 are returned after sorting name in DESC + * Verify product filtering using price range AND matching skus AND name sorted in DESC order * * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -675,8 +858,8 @@ public function testFilterProductsWithinSpecificPriceRangeSortedByNameDesc() filter: { price:{from: "5", to: "50"} - sku:{like:"simple%"} - name:{like:"Simple%"} + sku:{in:["simple1", "simple2"]} + name:{match:"Simple"} } pageSize:4 currentPage:1 @@ -798,7 +981,7 @@ public function testSearchWithFilterWithPageSizeEqualTotalCount() * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testQueryProductsInCurrentPageSortedByMultipleSortParameters() + public function testFilterByMultipleFilterFieldsSortedByMultipleSortFields() { $query = <<<QUERY @@ -807,8 +990,8 @@ public function testQueryProductsInCurrentPageSortedByMultipleSortParameters() filter: { price:{to :"50"} - sku:{like:"simple%"} - name:{like:"simple%"} + sku:{in:["simple1", "simple2"]} + name:{match:"Simple"} } pageSize:4 @@ -881,11 +1064,11 @@ public function testQueryProductsInCurrentPageSortedByMultipleSortParameters() } /** - * Verify the items is correct after sorting their name in ASC order + * Filtering products by fuzzy name match * - * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php + * @magentoApiDataFixture Magento/Catalog/_files/products_for_relevance_sorting.php */ - public function testQueryProductsSortedByNameASC() + public function testFilterProductsForExactMatchingName() { $query @@ -894,12 +1077,12 @@ public function testQueryProductsSortedByNameASC() products( filter: { - sku: { - like:"simple%" + name: { + match:"shorts" } } - pageSize:1 - currentPage:2 + pageSize:2 + currentPage:1 sort: { name:ASC @@ -916,6 +1099,16 @@ public function testQueryProductsSortedByNameASC() { page_size current_page + } + aggregations{ + attribute_code + count + label + options{ + label + value + count + } } } } @@ -924,15 +1117,53 @@ public function testQueryProductsSortedByNameASC() * @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $product = $productRepository->get('simple2'); - + $product1 = $productRepository->get('grey_shorts'); + $product2 = $productRepository->get('white_shorts'); $response = $this->graphQlQuery($query); $this->assertEquals(2, $response['products']['total_count']); - $this->assertEquals(['page_size' => 1, 'current_page' => 2], $response['products']['page_info']); - $this->assertEquals( - [['sku' => $product->getSku(), 'name' => $product->getName()]], + $this->assertEquals(['page_size' => 2, 'current_page' => 1], $response['products']['page_info']); + $this->assertEquals + ( + [ + ['sku' => $product1->getSku(), 'name' => $product1->getName()], + ['sku' => $product2->getSku(), 'name' => $product2->getName()] + ], $response['products']['items'] ); + $this->assertArrayHasKey('aggregations', $response['products']); + $this->assertCount(2, $response['products']['aggregations']); + $expectedAggregations =[ + [ + 'attribute_code' => 'price', + 'count' => 2, + 'label' => 'Price', + 'options' => [ + [ + 'label' => '10-20', + 'value' => '10_20', + 'count' => 1, + ], + [ + 'label' => '20-*', + 'value' => '20_*', + 'count' => 1, + ] + ] + ], + [ + 'attribute_code' => 'category_id', + 'count' => 1, + 'label' => 'Category', + 'options' => [ + [ + 'label' => 'Colorful Category', + 'value' => '330', + 'count' => 2, + ], + ], + ] + ]; + $this->assertEquals($expectedAggregations, $response['products']['aggregations']); } /** @@ -1060,12 +1291,19 @@ public function testFilterProductsBySingleCategoryId() /** * Sorting the search results by relevance (DESC => most relevant) * + * Search for products for a fuzzy match and checks if all matching results returned including + * results based on matching keywords from description + * * @magentoApiDataFixture Magento/Catalog/_files/products_for_relevance_sorting.php * @return void */ - public function testFilterProductsAndSortByRelevance() + public function testSearchAndSortByRelevance() { - $search_term ="red white blue grey socks"; + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); + $search_term ="blue"; $query = <<<QUERY { @@ -1098,24 +1336,39 @@ public function testFilterProductsAndSortByRelevance() value_string __typename } - } - + aggregations{ + attribute_code + count + label + options{ + label + value + count + } + } } } QUERY; $response = $this->graphQlQuery($query); - $this->assertEquals(2, $response['products']['total_count']); + $this->assertEquals(4, $response['products']['total_count']); + $this->assertNotEmpty($response['products']['filters'],'Filters should have the Category layer'); + $this->assertEquals('Colorful Category', $response['products']['filters'][0]['filter_items'][0]['label']); + $productsInResponse = ['Blue briefs', 'ocean blue Shoes', 'Navy Striped Shoes','Grey shorts']; + for ($i = 0; $i < count($response['products']['items']); $i++) { + $this->assertEquals($productsInResponse[$i], $response['products']['items'][$i]['name']); + } } /** - * Sorting by price in the DESC order from the filtered items with default pageSize + * Filtering for product with sku "equals" a specific value + * If pageSize and current page are not requested, default values are returned * * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testQuerySortByPriceDESCWithDefaultPageSize() + public function testFilterByExactSkuAndSortByPriceDesc() { $query = <<<QUERY @@ -1123,11 +1376,10 @@ public function testQuerySortByPriceDESCWithDefaultPageSize() products( filter: { - sku:{like:"simple%"} + sku:{eq:"simple1"} } sort: { - price:DESC } ) @@ -1162,25 +1414,38 @@ public function testQuerySortByPriceDESCWithDefaultPageSize() /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); $visibleProduct1 = $productRepository->get('simple1'); - $visibleProduct2 = $productRepository->get('simple2'); - $filteredProducts = [$visibleProduct2, $visibleProduct1]; + + $filteredProducts = [$visibleProduct1]; $response = $this->graphQlQuery($query); - $this->assertEquals(2, $response['products']['total_count']); + $this->assertEquals(1, $response['products']['total_count']); $this->assertProductItems($filteredProducts, $response); $this->assertEquals(20, $response['products']['page_info']['page_size']); $this->assertEquals(1, $response['products']['page_info']['current_page']); } /** - * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php + * Fuzzy search filtered for price and sorted by price and name + * + * @magentoApiDataFixture Magento/Catalog/_files/products_for_relevance_sorting.php */ public function testProductBasicFullTextSearchQuery() { - $textToSearch = 'Simple'; + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); + $textToSearch = 'blue'; $query =<<<QUERY { products( search: "{$textToSearch}" + filter:{ + price:{to:"50"} + } + sort:{ + price:DESC + name:ASC + } ) { total_count @@ -1200,13 +1465,30 @@ public function testProductBasicFullTextSearchQuery() page_size current_page } + filters{ + filter_items { + items_count + label + value_string + } + } + aggregations{ + attribute_code + count + label + options{ + count + label + value + } + } } } QUERY; /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $prod1 = $productRepository->get('simple1'); + $prod1 = $productRepository->get('blue_briefs'); $response = $this->graphQlQuery($query); $this->assertEquals(1, $response['products']['total_count']); @@ -1234,6 +1516,8 @@ public function testProductBasicFullTextSearchQuery() } /** + * Filter products purely in a given price range + * * @magentoApiDataFixture Magento/Catalog/_files/category.php * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php */ @@ -1242,8 +1526,8 @@ public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $prod1 = $productRepository->get('simple2'); - $prod2 = $productRepository->get('simple1'); + $prod1 = $productRepository->get('simple1'); + $prod2 = $productRepository->get('simple2'); $filteredProducts = [$prod1, $prod2]; /** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ $categoryLinkManagement = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() @@ -1268,7 +1552,7 @@ public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() currentPage:1 sort: { - price:DESC + price:ASC } ) { @@ -1322,7 +1606,7 @@ public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() $this->assertEquals(2, $response['products']['total_count']); $this->assertProductItemsWithPriceCheck($filteredProducts, $response); //verify that by default Price and category are the only layers available - $filterNames = ['Price', 'Category']; + $filterNames = ['Category', 'Price']; $this->assertCount(2, $response['products']['filters'], 'Filter count does not match'); for ($i = 0; $i < count($response['products']['filters']); $i++) { $this->assertEquals($filterNames[$i], $response['products']['filters'][$i]['name']); @@ -1344,8 +1628,8 @@ public function testQueryFilterNoMatchingItems() filter: { price:{from:"50"} - sku:{like:"simple%"} - name:{like:"simple%"} + + description:{match:"Description"} } pageSize:2 @@ -1449,6 +1733,7 @@ public function testQueryPageOutOfBoundException() } /** + * No filter or search arguments used * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testQueryWithNoSearchOrFilterArgumentException() @@ -1494,7 +1779,7 @@ public function testFilterProductsThatAreOutOfStockWithConfigSettings() products( filter: { - sku:{like:"simple%"} + sku:{eq:"simple_visible_in_stock"} } pageSize:20 @@ -1547,7 +1832,7 @@ public function testInvalidCurrentPage() products ( filter: { sku: { - like:"simple%" + eq:"simple1" } } pageSize: 4 @@ -1576,7 +1861,7 @@ public function testInvalidPageSize() products ( filter: { sku: { - like:"simple%" + eq:"simple2" } } pageSize: 0 diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php index 03a4baabf088e..27564d486c808 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php @@ -10,6 +10,7 @@ use Magento\TestFramework\Helper\Bootstrap; use Magento\Eav\Api\AttributeRepositoryInterface; +use Magento\TestFramework\Helper\CacheCleaner; $eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); @@ -27,18 +28,7 @@ /** @var AttributeRepositoryInterface $attributeRepository */ $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); $attributeRepository->save($attribute); - -/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ -$productRepository = Bootstrap::getObjectManager()->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); -$outOfStockChildProduct = $productRepository->get('simple_10'); -$outOfStockChildProduct->setStockData( - ['use_config_manage_stock' => 1, - 'qty' => 0, - 'is_qty_decimal' => 0, - 'is_in_stock' => 0] -); -$productRepository->save($outOfStockChildProduct); - +CacheCleaner::cleanAll(); /** @var \Magento\Indexer\Model\Indexer\Collection $indexerCollection */ $indexerCollection = Bootstrap::getObjectManager()->get(\Magento\Indexer\Model\Indexer\Collection::class); $indexerCollection->load(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute_rollback.php new file mode 100644 index 0000000000000..0ed7317762056 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/dropdown_attribute_rollback.php @@ -0,0 +1,18 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +/* Delete attribute with multiselect_attribute code */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ +$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + 'Magento\Catalog\Model\ResourceModel\Eav\Attribute' +); +$attribute->load('dropdown_attribute', 'attribute_code'); +$attribute->delete(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php index b8bdda92bc308..56f7d97e8c0fc 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php @@ -6,10 +6,26 @@ declare(strict_types=1); // phpcs:ignore Magento2.Security.IncludeFile -require __DIR__ . '/../../Framework/Search/_files/products.php'; +include __DIR__ . '/../../Framework/Search/_files/products.php'; use Magento\Catalog\Api\ProductRepositoryInterface; $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$categoryLinkRepository = $objectManager->create( + \Magento\Catalog\Api\CategoryLinkRepositoryInterface::class, + [ + 'productRepository' => $productRepository + ] +); +$categoryLinkManagement = $objectManager->create( + \Magento\Catalog\Api\CategoryLinkManagementInterface::class, + [ + 'productRepository' => $productRepository, + 'categoryLinkRepository' => $categoryLinkRepository + ] +); $category = $objectManager->create(\Magento\Catalog\Model\Category::class); $category->isObjectNew(true); $category->setId( @@ -21,7 +37,7 @@ )->setParentId( 2 )->setPath( - '1/2/300' + '1/2/330' )->setLevel( 2 )->setAvailableSortBy( @@ -45,31 +61,52 @@ ->setAttributeSetId($defaultAttributeSet) ->setStoreId(1) ->setWebsiteIds([1]) - ->setName('Red White and Blue striped Shoes') - ->setSku('red white and blue striped shoes') + ->setName('Navy Striped Shoes') + ->setSku('navy-striped-shoes') ->setPrice(40) ->setWeight(8) ->setDescription('Red white and blue flip flops at <b>one</b>') - ->setMetaTitle('Multi colored shoes meta title') - ->setMetaKeyword('red, white,flip-flops, women, kids') - ->setMetaDescription('flip flops women kids meta description') + ->setMetaTitle('navy colored shoes meta title') + ->setMetaKeyword('navy, striped , women, kids') + ->setMetaDescription('shoes women kids meta description') ->setStockData(['use_config_manage_stock' => 0]) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->save(); -/** @var ProductRepositoryInterface $productRepository */ -$productRepository = $objectManager->get(ProductRepositoryInterface::class); -$skus = ['green_socks', 'white_shorts','red_trousers','blue_briefs','grey_shorts', 'red white and blue striped shoes' ]; -$products = []; -foreach ($skus as $sku) { - $products = $productRepository->get($sku); -} + +/** @var $product \Magento\Catalog\Model\Product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->isObjectNew(true); +$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE) + ->setAttributeSetId($defaultAttributeSet) + ->setStoreId(1) + ->setWebsiteIds([1]) + ->setName('ocean blue Shoes') + ->setSku('ocean-blue-shoes') + ->setPrice(40) + ->setWeight(8) + ->setDescription('light blue shoes <b>one</b>') + ->setMetaTitle('light blue shoes meta title') + ->setMetaKeyword('light, blue , women, kids') + ->setMetaDescription('shoes women kids meta description') + ->setStockData(['use_config_manage_stock' => 0]) + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->save(); + +/** @var \Magento\Catalog\Model\Product $greyProduct */ +$greyProduct = $productRepository->get('grey_shorts'); +$greyProduct->setDescription('Description with blue lines'); +$productRepository->save($greyProduct); + +$skus = ['green_socks', 'white_shorts','red_trousers','blue_briefs','grey_shorts', + 'navy-striped-shoes', 'ocean-blue-shoes']; + /** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ - $categoryLinkManagement = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); -foreach ($products as $product) { +$categoryLinkManagement = $objectManager->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); +foreach ($skus as $sku) { $categoryLinkManagement->assignProductToCategories( - $product->getSku(), - [300] + $sku, + [330] ); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php new file mode 100644 index 0000000000000..90da9baf2d60e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $category \Magento\Catalog\Model\Category */ +$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); +$category->load(330); +if ($category->getId()) { + $category->delete(); +} +// Remove products +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productsToDelete = ['green_socks', 'white_shorts','red_trousers','blue_briefs', + 'grey_shorts', 'navy-striped-shoes','ocean-blue-shoes']; + +foreach ($productsToDelete as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + $productRepository->delete($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed + } +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php index 864a931359bdd..72336c48410d5 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute.php @@ -59,7 +59,7 @@ 'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']], 'order' => ['option_0' => 1, 'option_1' => 2], ], - 'default' => ['option_0'] + 'default_value' => 'option_0' ] ); From 433b517b5caad4b74a56b232801d9a5048b89351 Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Thu, 5 Sep 2019 15:04:44 -0500 Subject: [PATCH 659/841] MC-19713: Relative Category links created using PageBuilder have the store name as a URL parameter - Fix category/product link to different store should have a store code in the URL --- .../Magento/Catalog/Block/Widget/Link.php | 20 ++++++++++++ .../Test/Unit/Block/Widget/LinkTest.php | 32 +++++++++++++++---- 2 files changed, 45 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Widget/Link.php b/app/code/Magento/Catalog/Block/Widget/Link.php index 3d8a1cdf91ca2..a25af297111d2 100644 --- a/app/code/Magento/Catalog/Block/Widget/Link.php +++ b/app/code/Magento/Catalog/Block/Widget/Link.php @@ -89,12 +89,32 @@ public function getHref() if ($rewrite) { $href = $store->getUrl('', ['_direct' => $rewrite->getRequestPath()]); + + if ($this->addStoreCodeParam($store, $href)) { + $href .= (strpos($href, '?') === false ? '?' : '&') . '___store=' . $store->getCode(); + } } $this->_href = $href; } return $this->_href; } + /** + * Checks whether store code query param should be appended to the URL + * + * @param \Magento\Store\Model\Store $store + * @param string $url + * @return bool + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function addStoreCodeParam(\Magento\Store\Model\Store $store, string $url): bool + { + return $this->getStoreId() + && !$store->isUseStoreInUrl() + && $store->getId() !== $this->_storeManager->getStore()->getId() + && strpos($url, '___store') === false; + } + /** * Parse id_path * diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php index 3ceaf7dd44f57..dcbd3161733aa 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Widget/LinkTest.php @@ -141,16 +141,19 @@ public function testGetHrefIfRewriteIsNotFound() * * @dataProvider dataProviderForTestGetHrefWithoutUrlStoreSuffix * @param string $path + * @param int|null $storeId * @param bool $includeStoreCode * @param string $expected * @throws \ReflectionException */ public function testStoreCodeShouldBeIncludedInURLOnlyIfItIsConfiguredSo( string $path, + ?int $storeId, bool $includeStoreCode, string $expected ) { $this->block->setData('id_path', 'entity_type/entity_id'); + $this->block->setData('store_id', $storeId); $objectManager = new ObjectManager($this); $rewrite = $this->createPartialMock(UrlRewrite::class, ['getRequestPath']); @@ -192,17 +195,27 @@ public function testStoreCodeShouldBeIncludedInURLOnlyIfItIsConfiguredSo( $url->expects($this->any()) ->method('getUrl') ->willReturnCallback( - function ($route, $params) use ($store) { - return rtrim($store->getBaseUrl(), '/') .'/'. ltrim($params['_direct'], '/'); + function ($route, $params) use ($storeId) { + $baseUrl = rtrim($this->storeManager->getStore($storeId)->getBaseUrl(), '/'); + return $baseUrl .'/' . ltrim($params['_direct'], '/'); } ); $store->addData(['store_id' => 1, 'code' => 'french']); + $store2 = clone $store; + $store2->addData(['store_id' => 2, 'code' => 'german']); + $this->storeManager ->expects($this->any()) ->method('getStore') - ->willReturn($store); + ->willReturnMap( + [ + [null, $store], + [1, $store], + [2, $store2], + ] + ); $this->urlFinder->expects($this->once()) ->method('findOneByData') @@ -210,7 +223,7 @@ function ($route, $params) use ($store) { [ UrlRewrite::ENTITY_ID => 'entity_id', UrlRewrite::ENTITY_TYPE => 'entity_type', - UrlRewrite::STORE_ID => $store->getStoreId(), + UrlRewrite::STORE_ID => $this->storeManager->getStore($storeId)->getStoreId(), ] ) ->will($this->returnValue($rewrite)); @@ -219,7 +232,7 @@ function ($route, $params) use ($store) { ->method('getRequestPath') ->will($this->returnValue($path)); - $this->assertContains($expected, $this->block->getHref()); + $this->assertEquals($expected, $this->block->getHref()); } /** @@ -255,8 +268,13 @@ public function testGetLabelWithoutCustomText() public function dataProviderForTestGetHrefWithoutUrlStoreSuffix() { return [ - ['/accessories.html', true, 'french/accessories.html'], - ['/accessories.html', false, '/accessories.html'], + ['/accessories.html', null, true, 'french/accessories.html'], + ['/accessories.html', null, false, '/accessories.html'], + ['/accessories.html', 1, true, 'french/accessories.html'], + ['/accessories.html', 1, false, '/accessories.html'], + ['/accessories.html', 2, true, 'german/accessories.html'], + ['/accessories.html', 2, false, '/accessories.html?___store=german'], + ['/accessories.html?___store=german', 2, false, '/accessories.html?___store=german'], ]; } From 2667d9387c1291c0bb84751ea2566023e04d1734 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 5 Sep 2019 19:57:34 -0500 Subject: [PATCH 660/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - remove condition because it not the correct condition --- .../QuoteGraphQl/Model/Resolver/ShippingAddresses.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php index eebe2a66e92fd..76ffb99aeaab4 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php @@ -43,11 +43,6 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value /** @var Quote $cart */ $cart = $value['model']; - $addressesData = []; - if (!$cart->getExtensionAttributes()->getShippingAssignments()) { - return $addressesData; - } - $shippingAddresses = $cart->getAllShippingAddresses(); if (count($shippingAddresses)) { From a8cd0327b381bbf10e4fe03ed44cc796a4ffec69 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 5 Sep 2019 19:58:31 -0500 Subject: [PATCH 661/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - add validator to check if address was set --- .../Model/Resolver/ShippingAddresses.php | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php index 76ffb99aeaab4..4c299402780d5 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php @@ -13,6 +13,8 @@ use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Quote\Model\Quote; use Magento\QuoteGraphQl\Model\Cart\ExtractQuoteAddressData; +use Magento\Framework\GraphQl\Schema\Type\TypeRegistry; +use Magento\Framework\App\ObjectManager; /** * @inheritdoc @@ -24,12 +26,21 @@ class ShippingAddresses implements ResolverInterface */ private $extractQuoteAddressData; + /** + * @var TypeRegistry + */ + private $typeRegistry; + /** * @param ExtractQuoteAddressData $extractQuoteAddressData + * @param TypeRegistry|null $typeRegistry */ - public function __construct(ExtractQuoteAddressData $extractQuoteAddressData) - { + public function __construct( + ExtractQuoteAddressData $extractQuoteAddressData, + TypeRegistry $typeRegistry = null + ) { $this->extractQuoteAddressData = $extractQuoteAddressData; + $this->typeRegistry = $typeRegistry ?: ObjectManager::getInstance()->get(TypeRegistry::class); } /** @@ -47,9 +58,38 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value if (count($shippingAddresses)) { foreach ($shippingAddresses as $shippingAddress) { - $addressesData[] = $this->extractQuoteAddressData->execute($shippingAddress); + $address = $this->extractQuoteAddressData->execute($shippingAddress); + + if ($this->validateAddressFromSchema($address)) { + $addressesData[] = $address; + } } } return $addressesData; } + + /** + * Validate data from address against mandatory fields from graphql schema for address + * + * @param array $address + * @return bool + */ + private function validateAddressFromSchema(array $address) : bool + { + /** @var \Magento\Framework\GraphQL\Schema\Type\Input\InputObjectType $cartAddressInput */ + $cartAddressInput = $this->typeRegistry->get('CartAddressInput'); + $fields = $cartAddressInput->getFields(); + + foreach ($fields as $field) { + if ($field->getType() instanceof \Magento\Framework\GraphQL\Schema\Type\NonNull) { + // an array key has to exist but it's value should not be null + if (array_key_exists($field->name, $address) + && !is_array($address[$field->name]) + && !isset($address[$field->name])) { + return false; + } + } + } + return true; + } } From 3852a7e7b2e52f13b764c99bf566764b09c21204 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 5 Sep 2019 20:01:47 -0500 Subject: [PATCH 662/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - fix variable initialization --- .../Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php index 4c299402780d5..5968576e3a8dc 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php @@ -56,7 +56,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value $shippingAddresses = $cart->getAllShippingAddresses(); - if (count($shippingAddresses)) { + $addressesData = []; + if (!empty($shippingAddresses)) { foreach ($shippingAddresses as $shippingAddress) { $address = $this->extractQuoteAddressData->execute($shippingAddress); From 997e676a00fb1a4713ee2a2d587a2875633fda61 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 5 Sep 2019 21:12:04 -0500 Subject: [PATCH 663/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - fix tests --- .../GetSpecifiedShippingAddressTest.php | 39 +------------------ .../Guest/GetSpecifiedShippingAddressTest.php | 37 +----------------- 2 files changed, 2 insertions(+), 74 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php index 3b4e5edb917ca..d843748263ce4 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php @@ -77,22 +77,6 @@ public function testGetSpecifiedShippingAddress() self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); } - /** - * @magentoApiDataFixture Magento/Customer/_files/customer.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php - */ - public function testShippingAddressOnCreatedEmptyCart() - { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->getQuery($maskedQuoteId); - - $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap()); - self::assertArrayHasKey('cart', $response); - self::assertArrayHasKey('shipping_addresses', $response['cart']); - - self::assertCount(0, $response['cart']['shipping_addresses']); - } - /** * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php @@ -108,28 +92,7 @@ public function testGetSpecifiedShippingAddressIfShippingAddressIsNotSet() self::assertArrayHasKey('cart', $response); self::assertArrayHasKey('shipping_addresses', $response['cart']); - $expectedShippingAddressData = [ - 'firstname' => null, - 'lastname' => null, - 'company' => null, - 'street' => [ - '' - ], - 'city' => null, - 'region' => [ - 'code' => null, - 'label' => null, - ], - 'postcode' => null, - 'country' => [ - 'code' => null, - 'label' => null, - ], - 'telephone' => null, - '__typename' => 'ShippingCartAddress', - 'customer_notes' => null, - ]; - self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); + self::assertEquals(0, $response['cart']['shipping_addresses']); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php index 0809f9b136604..b5fa0d8f12dfc 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSpecifiedShippingAddressTest.php @@ -68,21 +68,6 @@ public function testGetSpecifiedShippingAddress() self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); } - /** - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php - */ - public function testShippingAddressOnCreatedEmptyCart() - { - $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); - $query = $this->getQuery($maskedQuoteId); - - $response = $this->graphQlQuery($query); - self::assertArrayHasKey('cart', $response); - self::assertArrayHasKey('shipping_addresses', $response['cart']); - - self::assertCount(0, $response['cart']['shipping_addresses']); - } - /** * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php @@ -97,27 +82,7 @@ public function testGetSpecifiedShippingAddressIfShippingAddressIsNotSet() self::assertArrayHasKey('cart', $response); self::assertArrayHasKey('shipping_addresses', $response['cart']); - $expectedShippingAddressData = [ - 'firstname' => null, - 'lastname' => null, - 'company' => null, - 'street' => [ - '' - ], - 'city' => null, - 'region' => [ - 'code' => null, - 'label' => null, - ], - 'postcode' => null, - 'country' => [ - 'code' => null, - 'label' => null, - ], - 'telephone' => null, - '__typename' => 'ShippingCartAddress', - ]; - self::assertEquals($expectedShippingAddressData, current($response['cart']['shipping_addresses'])); + self::assertEquals([], $response['cart']['shipping_addresses']); } /** From 9da6a58a6fa153f42c0029786291425ef217e7e9 Mon Sep 17 00:00:00 2001 From: Ivan Gerchak <ivang@ven.com> Date: Fri, 6 Sep 2019 10:25:44 +0300 Subject: [PATCH 664/841] Fix issue with "Apply Discount Code" input hidden, when code is applied --- .../view/frontend/templates/cart/coupon.phtml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml index bf8490affea0c..65dc514e476ff 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml @@ -7,10 +7,13 @@ /** * @var \Magento\Framework\View\Element\AbstractBlock $block */ + +// We should use strlen function because coupon code could be "0", converted to bool will lead to false +$hasCouponCode = (bool) strlen($block->getCouponCode()); ?> <div class="block discount" id="block-discount" - data-mage-init='{"collapsible":{"openedState": "active", "saveState": false}}' + data-mage-init='{"collapsible":{"active": <?= $hasCouponCode ? 'true' : 'false' ?>, "openedState": "active", "saveState": false}}' > <div class="title" data-role="title"> <strong id="block-discount-heading" role="heading" aria-level="2"><?= $block->escapeHtml(__('Apply Discount Code')) ?></strong> @@ -23,7 +26,7 @@ "removeCouponSelector": "#remove-coupon", "applyButton": "button.action.apply", "cancelButton": "button.action.cancel"}}'> - <div class="fieldset coupon<?= strlen($block->getCouponCode()) ? ' applied' : '' ?>"> + <div class="fieldset coupon<?= $hasCouponCode ? ' applied' : '' ?>"> <input type="hidden" name="remove" id="remove-coupon" value="0" /> <div class="field"> <label for="coupon_code" class="label"><span><?= $block->escapeHtml(__('Enter discount code')) ?></span></label> @@ -34,14 +37,14 @@ name="coupon_code" value="<?= $block->escapeHtmlAttr($block->getCouponCode()) ?>" placeholder="<?= $block->escapeHtmlAttr(__('Enter discount code')) ?>" - <?php if (strlen($block->getCouponCode())) :?> + <?php if ($hasCouponCode) :?> disabled="disabled" <?php endif; ?> /> </div> </div> <div class="actions-toolbar"> - <?php if (!strlen($block->getCouponCode())) :?> + <?php if (!$hasCouponCode) :?> <div class="primary"> <button class="action apply primary" type="button" value="<?= $block->escapeHtmlAttr(__('Apply Discount')) ?>"> <span><?= $block->escapeHtml(__('Apply Discount')) ?></span> @@ -54,7 +57,7 @@ <?php endif; ?> </div> </div> - <?php if (!strlen($block->getCouponCode())) : ?> + <?php if (!$hasCouponCode) : ?> <?= /* @noEscape */ $block->getChildHtml('captcha') ?> <?php endif; ?> </form> From 940a78e8ce1bc85fd443d842bd5326553af21864 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Fri, 6 Sep 2019 15:16:34 +0700 Subject: [PATCH 665/841] Add Unit Test for fix getRssUrl() when customer not login --- app/code/Magento/Wishlist/Helper/Data.php | 15 ++++++-- .../Wishlist/Test/Unit/Helper/DataTest.php | 38 ++++++++++++++++--- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Wishlist/Helper/Data.php b/app/code/Magento/Wishlist/Helper/Data.php index 2984d68f8deeb..e8280db3e1f21 100644 --- a/app/code/Magento/Wishlist/Helper/Data.php +++ b/app/code/Magento/Wishlist/Helper/Data.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Wishlist\Helper; use Magento\Framework\App\ActionInterface; @@ -117,6 +120,9 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper * @param \Magento\Customer\Helper\View $customerViewHelper * @param WishlistProviderInterface $wishlistProvider * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + * @param Escaper $escaper + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Helper\Context $context, @@ -127,7 +133,8 @@ public function __construct( \Magento\Framework\Data\Helper\PostHelper $postDataHelper, \Magento\Customer\Helper\View $customerViewHelper, WishlistProviderInterface $wishlistProvider, - \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, + Escaper $escaper = null ) { $this->_coreRegistry = $coreRegistry; $this->_customerSession = $customerSession; @@ -137,7 +144,7 @@ public function __construct( $this->_customerViewHelper = $customerViewHelper; $this->wishlistProvider = $wishlistProvider; $this->productRepository = $productRepository; - $this->escaper = ObjectManager::getInstance()->get(Escaper::class); + $this->escaper = $escaper ?? ObjectManager::getInstance()->get(Escaper::class); parent::__construct($context); } @@ -352,7 +359,6 @@ public function getAddParams($item, array $params = []) * Retrieve params for adding product to wishlist * * @param int $itemId - * * @return string */ public function getMoveFromCartParams($itemId) @@ -366,7 +372,6 @@ public function getMoveFromCartParams($itemId) * Retrieve params for updating product in wishlist * * @param \Magento\Catalog\Model\Product|\Magento\Wishlist\Model\Item $item - * * @return string|false */ public function getUpdateParams($item) @@ -541,6 +546,7 @@ public function getCustomerName() */ public function getRssUrl($wishlistId = null) { + $params = []; $customer = $this->_getCurrentCustomer(); if ($customer) { $key = $customer->getId() . ',' . $customer->getEmail(); @@ -574,6 +580,7 @@ public function getDefaultWishlistName() /** * Calculate count of wishlist items and put value to customer session. + * * Method called after wishlist modifications and trigger 'wishlist_items_renewed' event. * Depends from configuration. * diff --git a/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php b/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php index 1769306172aab..263f3c5d1688e 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Helper/DataTest.php @@ -18,6 +18,7 @@ use Magento\Wishlist\Controller\WishlistProviderInterface; use Magento\Wishlist\Model\Item as WishlistItem; use Magento\Wishlist\Model\Wishlist; +use Magento\Customer\Model\Session; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -63,6 +64,9 @@ class DataTest extends \PHPUnit\Framework\TestCase /** @var Context |\PHPUnit_Framework_MockObject_MockObject */ protected $context; + /** @var Session |\PHPUnit_Framework_MockObject_MockObject */ + protected $customerSession; + /** * Set up mock objects for tested class * @@ -121,12 +125,13 @@ protected function setUp() $this->wishlistItem = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) ->disableOriginalConstructor() - ->setMethods([ - 'getProduct', - 'getWishlistItemId', - 'getQty', - ]) - ->getMock(); + ->setMethods( + [ + 'getProduct', + 'getWishlistItemId', + 'getQty', + ] + )->getMock(); $this->wishlist = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class) ->disableOriginalConstructor() @@ -136,11 +141,16 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectManager->getObject( \Magento\Wishlist\Helper\Data::class, [ 'context' => $this->context, + 'customerSession' => $this->customerSession, 'storeManager' => $this->storeManager, 'wishlistProvider' => $this->wishlistProvider, 'coreRegistry' => $this->coreRegistry, @@ -431,4 +441,20 @@ public function testGetSharedAddAllToCartUrl() $this->assertEquals($url, $this->model->getSharedAddAllToCartUrl()); } + + public function testGetRssUrlWithCustomerNotLogin() + { + $url = 'result url'; + + $this->customerSession->expects($this->once()) + ->method('isLoggedIn') + ->willReturn(false); + + $this->urlBuilder->expects($this->once()) + ->method('getUrl') + ->with('wishlist/index/rss', []) + ->willReturn($url); + + $this->assertEquals($url, $this->model->getRssUrl()); + } } From ef7f98b674892c19dbf0d06f3e788fdd119696df Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Fri, 6 Sep 2019 11:16:34 +0300 Subject: [PATCH 666/841] MC-19637: Billing and Shipping information disappears after failed AJAX POST request --- app/code/Magento/Checkout/etc/frontend/sections.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Checkout/etc/frontend/sections.xml b/app/code/Magento/Checkout/etc/frontend/sections.xml index 90c2878f501cf..46dd8d9106545 100644 --- a/app/code/Magento/Checkout/etc/frontend/sections.xml +++ b/app/code/Magento/Checkout/etc/frontend/sections.xml @@ -41,7 +41,6 @@ </action> <action name="rest/*/V1/carts/*/payment-information"> <section name="cart"/> - <section name="checkout-data"/> <section name="last-ordered-items"/> </action> <action name="rest/*/V1/guest-carts/*/payment-information"> From d077b8f4fa16be51e681c32f953d8852049cc87a Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Fri, 6 Sep 2019 14:12:39 +0530 Subject: [PATCH 667/841] add validation greater than zero number for MySQL Message Queue Cleanup --- app/code/Magento/MysqlMq/etc/adminhtml/system.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/code/Magento/MysqlMq/etc/adminhtml/system.xml b/app/code/Magento/MysqlMq/etc/adminhtml/system.xml index 2684f2e0c98bf..045a176a48e87 100644 --- a/app/code/Magento/MysqlMq/etc/adminhtml/system.xml +++ b/app/code/Magento/MysqlMq/etc/adminhtml/system.xml @@ -13,15 +13,19 @@ <comment>All the times are in minutes. Use "0" if you want to skip automatic clearance.</comment> <field id="retry_inprogress_after" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Retry Messages In Progress After</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="successful_messages_lifetime" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Successful Messages Lifetime</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="failed_messages_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Failed Messages Lifetime</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="new_messages_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0"> <label>New Messages Lifetime</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> </group> </section> From 7fac8a3038b473a5db1d7ec97e7bb8e2552788ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 6 Sep 2019 10:46:13 +0200 Subject: [PATCH 668/841] Fix #21610 - No loading mask on sitemap, when click on Save --- .../Controller/Adminhtml/Sitemap/Edit.php | 21 ++++++++++++------- .../Sitemap/view/adminhtml/templates/js.phtml | 12 +++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php index 14771e7f03a3b..117f73311b644 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php @@ -6,25 +6,30 @@ namespace Magento\Sitemap\Controller\Adminhtml\Sitemap; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Block\Template; +use Magento\Backend\Model\Session; use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Registry; +use Magento\Sitemap\Controller\Adminhtml\Sitemap; /** * Controller class Edit. Responsible for rendering of a sitemap edit page */ -class Edit extends \Magento\Sitemap\Controller\Adminhtml\Sitemap implements HttpGetActionInterface +class Edit extends Sitemap implements HttpGetActionInterface { /** * Core registry * - * @var \Magento\Framework\Registry + * @var Registry */ - protected $_coreRegistry = null; + protected $_coreRegistry; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\Registry $coreRegistry + * @param Context $context + * @param Registry $coreRegistry */ - public function __construct(\Magento\Backend\App\Action\Context $context, \Magento\Framework\Registry $coreRegistry) + public function __construct(Context $context, Registry $coreRegistry) { $this->_coreRegistry = $coreRegistry; parent::__construct($context); @@ -53,7 +58,7 @@ public function execute() } // 3. Set entered data if was error when we do save - $data = $this->_objectManager->get(\Magento\Backend\Model\Session::class)->getFormData(true); + $data = $this->_objectManager->get(Session::class)->getFormData(true); if (!empty($data)) { $model->setData($data); } @@ -67,6 +72,8 @@ public function execute() $id ? __('Edit Sitemap') : __('New Sitemap') )->_addContent( $this->_view->getLayout()->createBlock(\Magento\Sitemap\Block\Adminhtml\Edit::class) + )->_addJs( + $this->_view->getLayout()->createBlock(Template::class)->setTemplate('Magento_Sitemap::js.phtml') ); $this->_view->getPage()->getConfig()->getTitle()->prepend(__('Site Map')); $this->_view->getPage()->getConfig()->getTitle()->prepend( diff --git a/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml b/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml new file mode 100644 index 0000000000000..4e42a7f7b779a --- /dev/null +++ b/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml @@ -0,0 +1,12 @@ +<script> + require( + ['jquery'], + function ($) { + $(document).on('save', 'form#edit_form', function () { + if ($(this).valid()) { + $('body').trigger('processStart'); + } + }); + } + ) +</script> From bc1657b298d55d024c3104cb94976019032f4f1f Mon Sep 17 00:00:00 2001 From: Sunil Patel <patelsunil42@gmail.com> Date: Fri, 6 Sep 2019 15:16:25 +0530 Subject: [PATCH 669/841] change word shopping cart to mini cart --- ...namicProductToShoppingCartWithDisableMiniCartSidebarTest.xml | 2 +- app/design/frontend/Magento/blank/web/css/source/_extends.less | 2 +- app/design/frontend/Magento/luma/web/css/source/_extends.less | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml index 9c00f2be1d60b..d67800e21afc2 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddBundleDynamicProductToShoppingCartWithDisableMiniCartSidebarTest.xml @@ -109,7 +109,7 @@ <argument name="total" value="110.00"/> </actionGroup> - <!--Enabled Shopping Cart Sidebar --> + <!--Enabled Mini Cart --> <magentoCLI stepKey="enableShoppingCartSidebar" command="config:set checkout/sidebar/display 1"/> <magentoCLI command="cache:flush" stepKey="flushCache"/> <reloadPage stepKey="reloadThePage"/> diff --git a/app/design/frontend/Magento/blank/web/css/source/_extends.less b/app/design/frontend/Magento/blank/web/css/source/_extends.less index a969d3499b51c..5bdaa4c3c35a3 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_extends.less +++ b/app/design/frontend/Magento/blank/web/css/source/_extends.less @@ -1290,7 +1290,7 @@ } // -// Shopping cart sidebar and checkout sidebar totals +// Mini Cart and checkout sidebar totals // --------------------------------------------- & when (@media-common = true) { diff --git a/app/design/frontend/Magento/luma/web/css/source/_extends.less b/app/design/frontend/Magento/luma/web/css/source/_extends.less index 81b7716d61ab7..ce86b690f6252 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_extends.less +++ b/app/design/frontend/Magento/luma/web/css/source/_extends.less @@ -1713,7 +1713,7 @@ } // -// Shopping cart sidebar and checkout sidebar totals +// Mini Cart and checkout sidebar totals // --------------------------------------------- & when (@media-common = true) { From f463caf1b77575c89ddc15320043f12d97284499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torben=20Ho=CC=88hn?= <torhoehn@gmail.com> Date: Fri, 6 Sep 2019 14:20:54 +0200 Subject: [PATCH 670/841] rework --- .../Block/Product/Renderer/Configurable.php | 60 +++++++++---------- 1 file changed, 27 insertions(+), 33 deletions(-) diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php index 35bf199681720..19d9340c4049c 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php @@ -61,12 +61,12 @@ class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\ /** * Config path which contains number of swatches per product */ - const XML_PATH_SWATCHES_PER_PRODUCT = 'catalog/frontend/swatches_per_product'; + private const XML_PATH_SWATCHES_PER_PRODUCT = 'catalog/frontend/swatches_per_product'; /** * Config path if swatch tooltips are enabled */ - const XML_PATH_SHOW_SWATCH_TOOLTIP = 'catalog/frontend/show_swatch_tooltip'; + private const XML_PATH_SHOW_SWATCH_TOOLTIP = 'catalog/frontend/show_swatch_tooltip'; /** * @var Product @@ -104,19 +104,19 @@ class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\ /** * @SuppressWarnings(PHPMD.ExcessiveParameterList) - * @param Context $context - * @param ArrayUtils $arrayUtils - * @param EncoderInterface $jsonEncoder - * @param Data $helper - * @param CatalogProduct $catalogProduct - * @param CurrentCustomer $currentCustomer - * @param PriceCurrencyInterface $priceCurrency - * @param ConfigurableAttributeData $configurableAttributeData - * @param SwatchData $swatchHelper - * @param Media $swatchMediaHelper - * @param array $data + * @param Context $context + * @param ArrayUtils $arrayUtils + * @param EncoderInterface $jsonEncoder + * @param Data $helper + * @param CatalogProduct $catalogProduct + * @param CurrentCustomer $currentCustomer + * @param PriceCurrencyInterface $priceCurrency + * @param ConfigurableAttributeData $configurableAttributeData + * @param SwatchData $swatchHelper + * @param Media $swatchMediaHelper + * @param array $data * @param SwatchAttributesProvider|null $swatchAttributesProvider - * @param UrlBuilder|null $imageUrlBuilder + * @param UrlBuilder|null $imageUrlBuilder */ public function __construct( Context $context, @@ -183,6 +183,7 @@ public function getJsonSwatchConfig() $attributesData = $this->getSwatchAttributesData(); $allOptionIds = $this->getConfigurableOptionsIds($attributesData); $swatchesData = $this->swatchHelper->getSwatchesByOptionsId($allOptionIds); + $config = []; foreach ($attributesData as $attributeId => $attributeDataArray) { if (isset($attributeDataArray['options'])) { @@ -232,13 +233,11 @@ public function getShowSwatchTooltip() * Set product to block * * @param Product $product - * * @return $this */ public function setProduct(Product $product) { $this->product = $product; - return $this; } @@ -269,10 +268,10 @@ protected function getSwatchAttributesData() /** * Init isProductHasSwatchAttribute. * - * @return void * @deprecated 100.1.5 Method isProductHasSwatchAttribute() is used instead of this. * * @codeCoverageIgnore + * @return void */ protected function initIsProductHasSwatchAttribute() { @@ -288,7 +287,6 @@ protected function initIsProductHasSwatchAttribute() protected function isProductHasSwatchAttribute() { $swatchAttributes = $this->swatchAttributesProvider->provide($this->getProduct()); - return count($swatchAttributes) > 0; } @@ -298,7 +296,6 @@ protected function isProductHasSwatchAttribute() * @param array $options * @param array $swatchesCollectionArray * @param array $attributeDataArray - * * @return array */ protected function addSwatchDataForAttribute( @@ -321,10 +318,9 @@ protected function addSwatchDataForAttribute( /** * Add media from variation * - * @param array $swatch + * @param array $swatch * @param integer $optionId - * @param array $attributeDataArray - * + * @param array $attributeDataArray * @return array */ protected function addAdditionalMediaData(array $swatch, $optionId, array $attributeDataArray) @@ -333,12 +329,11 @@ protected function addAdditionalMediaData(array $swatch, $optionId, array $attri && $attributeDataArray['use_product_image_for_swatch'] ) { $variationMedia = $this->getVariationMedia($attributeDataArray['attribute_code'], $optionId); - if (!empty($variationMedia)) { + if (! empty($variationMedia)) { $swatch['type'] = Swatch::SWATCH_TYPE_VISUAL_IMAGE; $swatch = array_merge($swatch, $variationMedia); } } - return $swatch; } @@ -346,12 +341,12 @@ protected function addAdditionalMediaData(array $swatch, $optionId, array $attri * Retrieve Swatch data for config * * @param array $swatchDataArray - * * @return array */ protected function extractNecessarySwatchData(array $swatchDataArray) { $result['type'] = $swatchDataArray['type']; + if ($result['type'] == Swatch::SWATCH_TYPE_VISUAL_IMAGE && !empty($swatchDataArray['value'])) { $result['value'] = $this->swatchMediaHelper->getSwatchAttributeImage( Swatch::SWATCH_IMAGE_NAME, @@ -371,9 +366,8 @@ protected function extractNecessarySwatchData(array $swatchDataArray) /** * Generate Product Media array * - * @param string $attributeCode + * @param string $attributeCode * @param integer $optionId - * * @return array */ protected function getVariationMedia($attributeCode, $optionId) @@ -382,12 +376,14 @@ protected function getVariationMedia($attributeCode, $optionId) $this->getProduct(), [$attributeCode => $optionId] ); + if (!$variationProduct) { $variationProduct = $this->swatchHelper->loadFirstVariationWithImage( $this->getProduct(), [$attributeCode => $optionId] ); } + $variationMediaArray = []; if ($variationProduct) { $variationMediaArray = [ @@ -403,8 +399,7 @@ protected function getVariationMedia($attributeCode, $optionId) * Get swatch product image. * * @param Product $childProduct - * @param string $imageType - * + * @param string $imageType * @return string */ protected function getSwatchProductImage(Product $childProduct, $imageType) @@ -416,6 +411,7 @@ protected function getSwatchProductImage(Product $childProduct, $imageType) $swatchImageId = $imageType == Swatch::SWATCH_IMAGE_NAME ? 'swatch_image_base' : 'swatch_thumb_base'; $imageAttributes = ['type' => 'image']; } + if (!empty($swatchImageId) && !empty($imageAttributes['type'])) { return $this->imageUrlBuilder->getUrl($childProduct->getData($imageAttributes['type']), $swatchImageId); } @@ -425,8 +421,7 @@ protected function getSwatchProductImage(Product $childProduct, $imageType) * Check if product have image. * * @param Product $product - * @param string $imageType - * + * @param string $imageType * @return bool */ protected function isProductHasImage(Product $product, $imageType) @@ -438,7 +433,6 @@ protected function isProductHasImage(Product $product, $imageType) * Get configurable options ids. * * @param array $attributeData - * * @return array * @since 100.0.3 */ @@ -455,7 +449,6 @@ protected function getConfigurableOptionsIds(array $attributeData) } } } - return array_keys($ids); } @@ -540,6 +533,7 @@ public function getJsonSwatchSizeConfig() { $imageConfig = $this->swatchMediaHelper->getImageConfig(); $sizeConfig = []; + $sizeConfig[self::SWATCH_IMAGE_NAME]['width'] = $imageConfig[Swatch::SWATCH_IMAGE_NAME]['width']; $sizeConfig[self::SWATCH_IMAGE_NAME]['height'] = $imageConfig[Swatch::SWATCH_IMAGE_NAME]['height']; $sizeConfig[self::SWATCH_THUMBNAIL_NAME]['height'] = $imageConfig[Swatch::SWATCH_THUMBNAIL_NAME]['height']; From 2ea7a99f9684597d36cc7422d5d0f6ebf3ea4f4f Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Fri, 6 Sep 2019 11:47:56 +0300 Subject: [PATCH 671/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix CR comments. --- app/code/Magento/Catalog/Model/ResourceModel/Category.php | 6 +++++- .../Catalog/Model/ResourceModel/Product/Collection.php | 4 ++++ .../Catalog/Model/ResourceModel/Product/CollectionTest.php | 7 +++++-- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index fb8c089ed9f9a..797ce72ae9b7a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -1155,6 +1155,10 @@ public function getCategoryWithChildren(int $categoryId): array )->where('entity_type_id = ?', CategorySetup::CATEGORY_ENTITY_TYPE_ID) ->where('attribute_code = ?', 'is_anchor') ->limit(1); + $isAnchorAttributeCode = $connection->fetchOne($selectAttributeCode); + if (empty($isAnchorAttributeCode) || (int)$isAnchorAttributeCode <= 0) { + return []; + } $select = $connection->select() ->from( @@ -1166,7 +1170,7 @@ public function getCategoryWithChildren(int $categoryId): array ['is_anchor' => 'cce_int.value'] )->where( 'cce_int.attribute_id = ?', - $connection->fetchOne($selectAttributeCode) + $isAnchorAttributeCode )->where( "cce.path LIKE '%/{$categoryId}' OR cce.path LIKE '%/{$categoryId}/%'" )->order('path'); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 71b03620b342b..42d55892b6ec6 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -2107,6 +2107,10 @@ private function getChildrenCategories(int $categoryId): array $anchorCategory = []; $categories = $this->categoryResourceModel->getCategoryWithChildren($categoryId); + if (empty($categories)) { + return $categoryIds; + } + $firstCategory = array_shift($categories); if ($firstCategory['is_anchor'] == 1) { $anchorCategory[] = (int)$firstCategory['entity_id']; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php index 80d700c7e54ff..d3970ad403ffd 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php @@ -99,6 +99,7 @@ public function testAddPriceDataOnSchedule() /** * @magentoDataFixture Magento/Catalog/_files/products.php + * @magentoAppIsolation enabled * @magentoDbIsolation disabled */ public function testSetVisibility() @@ -116,6 +117,7 @@ public function testSetVisibility() /** * @magentoDataFixture Magento/Catalog/_files/category_product.php + * @magentoAppIsolation enabled * @magentoDbIsolation disabled */ public function testSetCategoryWithStoreFilter() @@ -131,8 +133,8 @@ public function testSetCategoryWithStoreFilter() $this->collection->load(); $collectionStoreFilterAfter = Bootstrap::getObjectManager()->create( - \Magento\Catalog\Model\ResourceModel\Product\Collection::class - ); + \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class + )->create(); $collectionStoreFilterAfter->addStoreFilter(1)->addCategoryFilter($category); $collectionStoreFilterAfter->load(); $this->assertEquals($this->collection->getItems(), $collectionStoreFilterAfter->getItems()); @@ -141,6 +143,7 @@ public function testSetCategoryWithStoreFilter() /** * @magentoDataFixture Magento/Catalog/_files/categories.php + * @magentoAppIsolation enabled * @magentoDbIsolation disabled */ public function testSetCategoryFilter() From 17f291b465b5481ffa0ca221593114db01ff8ae2 Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Thu, 5 Sep 2019 14:21:27 -0500 Subject: [PATCH 672/841] MC-19749: Store creation ('app:config:import' with pre-defined stores) fails during upgrade - Fix object manager provider reset causes multiple connection to database --- .../Setup/Model/ObjectManagerProviderTest.php | 31 +++++++++++++--- .../Magento/Framework/Console/Cli.php | 1 + .../Setup/Model/ObjectManagerProvider.php | 5 ++- .../Unit/Model/ObjectManagerProviderTest.php | 35 ++++++++++--------- 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php b/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php index c4fc0fa7c5854..a80da16be67eb 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Model/ObjectManagerProviderTest.php @@ -6,9 +6,17 @@ namespace Magento\Setup\Model; +use Magento\Framework\ObjectManagerInterface; use Magento\Setup\Mvc\Bootstrap\InitParamListener; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; +use Symfony\Component\Console\Application; +use Zend\ServiceManager\ServiceLocatorInterface; -class ObjectManagerProviderTest extends \PHPUnit\Framework\TestCase +/** + * Tests ObjectManagerProvider + */ +class ObjectManagerProviderTest extends TestCase { /** * @var ObjectManagerProvider @@ -16,21 +24,34 @@ class ObjectManagerProviderTest extends \PHPUnit\Framework\TestCase private $object; /** - * @var \Zend\ServiceManager\ServiceLocatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ServiceLocatorInterface|PHPUnit_Framework_MockObject_MockObject */ private $locator; + /** + * @inheritDoc + */ protected function setUp() { - $this->locator = $this->getMockForAbstractClass(\Zend\ServiceManager\ServiceLocatorInterface::class); + $this->locator = $this->getMockForAbstractClass(ServiceLocatorInterface::class); $this->object = new ObjectManagerProvider($this->locator, new Bootstrap()); + $this->locator->expects($this->any()) + ->method('get') + ->willReturnMap( + [ + [InitParamListener::BOOTSTRAP_PARAM, []], + [Application::class, $this->getMockForAbstractClass(Application::class)], + ] + ); } + /** + * Tests the same instance of ObjectManagerInterface should be provided by the ObjectManagerProvider + */ public function testGet() { - $this->locator->expects($this->once())->method('get')->with(InitParamListener::BOOTSTRAP_PARAM)->willReturn([]); $objectManager = $this->object->get(); - $this->assertInstanceOf(\Magento\Framework\ObjectManagerInterface::class, $objectManager); + $this->assertInstanceOf(ObjectManagerInterface::class, $objectManager); $this->assertSame($objectManager, $this->object->get()); } } diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php index 2ef41f361027e..34fd6316ce454 100644 --- a/lib/internal/Magento/Framework/Console/Cli.php +++ b/lib/internal/Magento/Framework/Console/Cli.php @@ -93,6 +93,7 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') } parent::__construct($name, $version); + $this->serviceManager->setService(\Symfony\Component\Console\Application::class, $this); } /** diff --git a/setup/src/Magento/Setup/Model/ObjectManagerProvider.php b/setup/src/Magento/Setup/Model/ObjectManagerProvider.php index e25b976e9207f..79216c8ec89b5 100644 --- a/setup/src/Magento/Setup/Model/ObjectManagerProvider.php +++ b/setup/src/Magento/Setup/Model/ObjectManagerProvider.php @@ -76,10 +76,9 @@ private function createCliCommands() { /** @var CommandListInterface $commandList */ $commandList = $this->objectManager->create(CommandListInterface::class); + $application = $this->serviceLocator->get(Application::class); foreach ($commandList->getCommands() as $command) { - $command->setApplication( - $this->serviceLocator->get(Application::class) - ); + $application->add($command); } } diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php index 9d40b053e394e..552453c4a185c 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ObjectManagerProviderTest.php @@ -47,6 +47,14 @@ public function setUp() public function testGet() { $initParams = ['param' => 'value']; + $commands = [ + new Command('setup:install'), + new Command('setup:upgrade'), + ]; + + $application = $this->getMockBuilder(Application::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); $this->serviceLocatorMock ->expects($this->atLeastOnce()) @@ -56,16 +64,21 @@ public function testGet() [InitParamListener::BOOTSTRAP_PARAM, $initParams], [ Application::class, - $this->getMockBuilder(Application::class)->disableOriginalConstructor()->getMock(), + $application, ], ] ); + $commandListMock = $this->createMock(CommandListInterface::class); + $commandListMock->expects($this->once()) + ->method('getCommands') + ->willReturn($commands); + $objectManagerMock = $this->createMock(ObjectManagerInterface::class); $objectManagerMock->expects($this->once()) ->method('create') ->with(CommandListInterface::class) - ->willReturn($this->getCommandListMock()); + ->willReturn($commandListMock); $objectManagerFactoryMock = $this->getMockBuilder(ObjectManagerFactory::class) ->disableOriginalConstructor() @@ -81,21 +94,9 @@ public function testGet() ->willReturn($objectManagerFactoryMock); $this->assertInstanceOf(ObjectManagerInterface::class, $this->model->get()); - } - - /** - * @return \PHPUnit_Framework_MockObject_MockObject - */ - private function getCommandListMock() - { - $commandMock = $this->getMockBuilder(Command::class)->disableOriginalConstructor()->getMock(); - $commandMock->expects($this->once())->method('setApplication'); - - $commandListMock = $this->createMock(CommandListInterface::class); - $commandListMock->expects($this->once()) - ->method('getCommands') - ->willReturn([$commandMock]); - return $commandListMock; + foreach ($commands as $command) { + $this->assertSame($application, $command->getApplication()); + } } } From 4dad2e5b010b26308cce4ea0c4465a3e50d330d7 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 6 Sep 2019 09:33:19 -0500 Subject: [PATCH 673/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - fix changes to also apply to 2.3.2 --- .../Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php index 5968576e3a8dc..ae97f7efda45f 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php @@ -54,10 +54,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value /** @var Quote $cart */ $cart = $value['model']; + $addressesData = []; $shippingAddresses = $cart->getAllShippingAddresses(); - $addressesData = []; - if (!empty($shippingAddresses)) { + if (count($shippingAddresses)) { foreach ($shippingAddresses as $shippingAddress) { $address = $this->extractQuoteAddressData->execute($shippingAddress); From 860e239812ca605229e107f65a967f28a33e032a Mon Sep 17 00:00:00 2001 From: Maksym Novik <maksym.novik@decimadigital.com> Date: Fri, 6 Sep 2019 18:46:07 +0300 Subject: [PATCH 674/841] GraphQl-220: Implement exception logging. Removed duplicated logging to var/log/graphql. --- app/etc/graphql/di.xml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/app/etc/graphql/di.xml b/app/etc/graphql/di.xml index 4370c3b445f65..aba60d00080ff 100644 --- a/app/etc/graphql/di.xml +++ b/app/etc/graphql/di.xml @@ -7,21 +7,4 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Framework\GraphQl\Query\ErrorHandlerInterface" type="Magento\Framework\GraphQl\Query\ErrorHandler"/> - <virtualType name="GraphQlLogger" type="Magento\Framework\Logger\Monolog"> - <arguments> - <argument name="handlers" xsi:type="array"> - <item name="error" xsi:type="object">GraphQlErrorHandler</item> - </argument> - </arguments> - </virtualType> - <virtualType name="GraphQlErrorHandler" type="Magento\Framework\Logger\Handler\Base"> - <arguments> - <argument name="fileName" xsi:type="string">var/log/graphql/exception.log</argument> - </arguments> - </virtualType> - <type name="Magento\Framework\GraphQl\Query\ErrorHandler"> - <arguments> - <argument name="logger" xsi:type="object">GraphQlLogger</argument> - </arguments> - </type> </config> From 08d29e43ff2af78ac23c2583bcc7840673589fdb Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Fri, 6 Sep 2019 11:34:30 -0500 Subject: [PATCH 675/841] Update AdvancedPricingTest.php --- .../Test/Unit/Model/Import/AdvancedPricingTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php index 9fac1c968dfc9..fd968a2682d58 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php @@ -15,6 +15,9 @@ */ class AdvancedPricingTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractImportTestCase { + /** + * DB Table data + */ const TABLE_NAME = 'tableName'; const LINK_FIELD = 'linkField'; From e7fb5d79b9ca5559ffc987aed3748aec7bc4f377 Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Fri, 6 Sep 2019 19:33:08 +0300 Subject: [PATCH 676/841] magento/magento2#: Fix release notification README --- app/code/Magento/ReleaseNotification/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/ReleaseNotification/README.md b/app/code/Magento/ReleaseNotification/README.md index 1f6cac764b565..c53e3cbde1d0f 100644 --- a/app/code/Magento/ReleaseNotification/README.md +++ b/app/code/Magento/ReleaseNotification/README.md @@ -1,4 +1,4 @@ - # Magento_ReleaseNotification Module +# Magento_ReleaseNotification module The **Release Notification Module** serves to provide a notification delivery platform for displaying new features of a Magento installation or upgrade as well as any other required release notifications. From 2f04e666aceba9bcbd863b78613714922a1f0f35 Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk <odubovyk@magento.com> Date: Fri, 6 Sep 2019 12:42:04 -0500 Subject: [PATCH 677/841] MC-19798: 'Quote Lifetime (days)' setting does not work - fixed --- app/code/Magento/Sales/Cron/CleanExpiredQuotes.php | 1 - .../Magento/Sales/Test/Unit/Cron/CleanExpiredQuotesTest.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/code/Magento/Sales/Cron/CleanExpiredQuotes.php b/app/code/Magento/Sales/Cron/CleanExpiredQuotes.php index 021e7b66cd13f..a5c7f71df66c5 100644 --- a/app/code/Magento/Sales/Cron/CleanExpiredQuotes.php +++ b/app/code/Magento/Sales/Cron/CleanExpiredQuotes.php @@ -57,7 +57,6 @@ public function execute() $quotes->addFieldToFilter('store_id', $storeId); $quotes->addFieldToFilter('updated_at', ['to' => date("Y-m-d", time() - $lifetime)]); - $quotes->addFieldToFilter('is_active', 0); foreach ($this->getExpireQuotesAdditionalFilterFields() as $field => $condition) { $quotes->addFieldToFilter($field, $condition); diff --git a/app/code/Magento/Sales/Test/Unit/Cron/CleanExpiredQuotesTest.php b/app/code/Magento/Sales/Test/Unit/Cron/CleanExpiredQuotesTest.php index e424cae85f223..ad6a3e03ba679 100644 --- a/app/code/Magento/Sales/Test/Unit/Cron/CleanExpiredQuotesTest.php +++ b/app/code/Magento/Sales/Test/Unit/Cron/CleanExpiredQuotesTest.php @@ -59,7 +59,7 @@ public function testExecute($lifetimes, $additionalFilterFields) $this->quoteFactoryMock->expects($this->exactly(count($lifetimes))) ->method('create') ->will($this->returnValue($quotesMock)); - $quotesMock->expects($this->exactly((3 + count($additionalFilterFields)) * count($lifetimes))) + $quotesMock->expects($this->exactly((2 + count($additionalFilterFields)) * count($lifetimes))) ->method('addFieldToFilter'); if (!empty($lifetimes)) { $quotesMock->expects($this->exactly(count($lifetimes))) From 2b5a39b6088e34a11ac14e0f19f3b8bcdd4ca866 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 6 Sep 2019 13:39:23 -0500 Subject: [PATCH 678/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - fix test --- .../GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php index d843748263ce4..2023603a21eed 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSpecifiedShippingAddressTest.php @@ -92,7 +92,7 @@ public function testGetSpecifiedShippingAddressIfShippingAddressIsNotSet() self::assertArrayHasKey('cart', $response); self::assertArrayHasKey('shipping_addresses', $response['cart']); - self::assertEquals(0, $response['cart']['shipping_addresses']); + self::assertEquals([], $response['cart']['shipping_addresses']); } /** From 7852b00a993ff176aedaba68f96646dedd5c583d Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 6 Sep 2019 14:00:21 -0500 Subject: [PATCH 679/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - fix test --- .../GetSelectedShippingMethodTest.php | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php index 9bb36bf8f0929..c8a80c493e282 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php @@ -108,18 +108,7 @@ public function testGetSelectedShippingMethodBeforeSet() $shippingAddress = current($response['cart']['shipping_addresses']); self::assertArrayHasKey('selected_shipping_method', $shippingAddress); - - self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['carrier_code']); - - self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['method_code']); - - self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['carrier_title']); - - self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['method_title']); + self::assertNull($shippingAddress['selected_shipping_method']); } /** @@ -182,13 +171,7 @@ public function testGetGetSelectedShippingMethodIfShippingMethodIsNotSet() $shippingAddress = current($response['cart']['shipping_addresses']); self::assertArrayHasKey('selected_shipping_method', $shippingAddress); - - self::assertNull($shippingAddress['selected_shipping_method']['carrier_code']); - self::assertNull($shippingAddress['selected_shipping_method']['method_code']); - self::assertNull($shippingAddress['selected_shipping_method']['carrier_title']); - self::assertNull($shippingAddress['selected_shipping_method']['method_title']); - self::assertNull($shippingAddress['selected_shipping_method']['amount']); - self::assertNull($shippingAddress['selected_shipping_method']['base_amount']); + self::assertNull('selected_shipping_method', $shippingAddress['selected_shipping_method']); } /** From af414b085fa7c13267169aaafc79675448872c88 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 6 Sep 2019 14:57:54 -0500 Subject: [PATCH 680/841] MC-19746: The shipping address contains `null` values when there's no shipping address set on a cart - fix test --- .../Customer/GetSelectedShippingMethodTest.php | 2 +- .../Quote/Guest/GetSelectedShippingMethodTest.php | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php index c8a80c493e282..90107ad2220da 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetSelectedShippingMethodTest.php @@ -171,7 +171,7 @@ public function testGetGetSelectedShippingMethodIfShippingMethodIsNotSet() $shippingAddress = current($response['cart']['shipping_addresses']); self::assertArrayHasKey('selected_shipping_method', $shippingAddress); - self::assertNull('selected_shipping_method', $shippingAddress['selected_shipping_method']); + self::assertNull($shippingAddress['selected_shipping_method']); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php index 5d1033b39819e..fb694508a643f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetSelectedShippingMethodTest.php @@ -99,18 +99,7 @@ public function testGetSelectedShippingMethodBeforeSet() $shippingAddress = current($response['cart']['shipping_addresses']); self::assertArrayHasKey('selected_shipping_method', $shippingAddress); - - self::assertArrayHasKey('carrier_code', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['carrier_code']); - - self::assertArrayHasKey('method_code', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['method_code']); - - self::assertArrayHasKey('carrier_title', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['carrier_title']); - - self::assertArrayHasKey('method_title', $shippingAddress['selected_shipping_method']); - self::assertNull($shippingAddress['selected_shipping_method']['method_title']); + self::assertNull($shippingAddress['selected_shipping_method']); } /** From 3f87c97ac9ba14fb93656150db22a0609b743bd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Szubert?= <bartlomiejszubert@gmail.com> Date: Fri, 6 Sep 2019 22:13:35 +0200 Subject: [PATCH 681/841] Fix #21610 - replace inline js with x-magento-init --- .../Sitemap/view/adminhtml/templates/js.phtml | 15 +++++---------- .../adminhtml/web/js/form-submit-loader.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) create mode 100644 app/code/Magento/Sitemap/view/adminhtml/web/js/form-submit-loader.js diff --git a/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml b/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml index 4e42a7f7b779a..480615cba67de 100644 --- a/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml +++ b/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml @@ -1,12 +1,7 @@ -<script> - require( - ['jquery'], - function ($) { - $(document).on('save', 'form#edit_form', function () { - if ($(this).valid()) { - $('body').trigger('processStart'); - } - }); +<script type="text/x-magento-init"> + { + "#edit_form": { + "Magento_Sitemap/js/form-submit-loader" : {} } - ) + } </script> diff --git a/app/code/Magento/Sitemap/view/adminhtml/web/js/form-submit-loader.js b/app/code/Magento/Sitemap/view/adminhtml/web/js/form-submit-loader.js new file mode 100644 index 0000000000000..6b7ba6be65b22 --- /dev/null +++ b/app/code/Magento/Sitemap/view/adminhtml/web/js/form-submit-loader.js @@ -0,0 +1,19 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery' +], function ($) { + 'use strict'; + + return function (data, element) { + + $(element).on('save', function () { + if ($(this).valid()) { + $('body').trigger('processStart'); + } + }); + }; +}); From a579c208029c5352d09038665ef7e0d03f3d9d55 Mon Sep 17 00:00:00 2001 From: mahesh <mahesh721@webkul.com> Date: Sat, 7 Sep 2019 16:44:54 +0530 Subject: [PATCH 682/841] Fixed issue:24031 Layer navigation displaying whenever select Static Block only --- .../LayeredNavigation/Block/Navigation.php | 3 +- .../Test/Unit/Block/NavigationTest.php | 54 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/LayeredNavigation/Block/Navigation.php b/app/code/Magento/LayeredNavigation/Block/Navigation.php index 4173469da8e42..d4709ac05426f 100644 --- a/app/code/Magento/LayeredNavigation/Block/Navigation.php +++ b/app/code/Magento/LayeredNavigation/Block/Navigation.php @@ -107,7 +107,8 @@ public function getFilters() */ public function canShowBlock() { - return $this->visibilityFlag->isEnabled($this->getLayer(), $this->getFilters()); + return $this->getLayer()->getCurrentCategory()->getDisplayMode() !== \Magento\Catalog\Model\Category::DM_PAGE + && $this->visibilityFlag->isEnabled($this->getLayer(), $this->getFilters()); } /** diff --git a/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php b/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php index e37e58b14f027..f0243784dd618 100644 --- a/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php +++ b/app/code/Magento/LayeredNavigation/Test/Unit/Block/NavigationTest.php @@ -6,6 +6,8 @@ namespace Magento\LayeredNavigation\Test\Unit\Block; +use Magento\Catalog\Model\Category; + class NavigationTest extends \PHPUnit\Framework\TestCase { /** @@ -98,9 +100,61 @@ public function testCanShowBlock() ->method('isEnabled') ->with($this->catalogLayerMock, $filters) ->will($this->returnValue($enabled)); + + $category = $this->createMock(Category::class); + $this->catalogLayerMock->expects($this->atLeastOnce())->method('getCurrentCategory')->willReturn($category); + $category->expects($this->once())->method('getDisplayMode')->willReturn(Category::DM_PRODUCT); + $this->assertEquals($enabled, $this->model->canShowBlock()); } + /** + * Test canShowBlock() with different category display types. + * + * @param string $mode + * @param bool $result + * + * @dataProvider canShowBlockDataProvider + */ + public function testCanShowBlockWithDifferentDisplayModes(string $mode, bool $result) + { + $filters = ['To' => 'be', 'or' => 'not', 'to' => 'be']; + + $this->filterListMock->expects($this->atLeastOnce())->method('getFilters') + ->with($this->catalogLayerMock) + ->will($this->returnValue($filters)); + $this->assertEquals($filters, $this->model->getFilters()); + + $this->visibilityFlagMock + ->expects($this->any()) + ->method('isEnabled') + ->with($this->catalogLayerMock, $filters) + ->will($this->returnValue(true)); + + $category = $this->createMock(Category::class); + $this->catalogLayerMock->expects($this->atLeastOnce())->method('getCurrentCategory')->willReturn($category); + $category->expects($this->once())->method('getDisplayMode')->willReturn($mode); + $this->assertEquals($result, $this->model->canShowBlock()); + } + + public function canShowBlockDataProvider() + { + return [ + [ + Category::DM_PRODUCT, + true, + ], + [ + Category::DM_PAGE, + false, + ], + [ + Category::DM_MIXED, + true, + ], + ]; + } + public function testGetClearUrl() { $this->filterListMock->expects($this->any())->method('getFilters')->will($this->returnValue([])); From 7005d99ae4af1f3ffdf35ba8370c4295c0bfbfe6 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sat, 7 Sep 2019 18:21:11 +0700 Subject: [PATCH 683/841] Resolve "Clear Shopping Cart" button not working in Internet Explorer issue24491 --- .../Checkout/view/frontend/templates/cart/form.phtml | 2 +- .../Magento/Checkout/view/frontend/web/js/shopping-cart.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml index e1ab036c7d889..370d70c44d886 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml @@ -56,7 +56,7 @@ <span><?= $block->escapeHtml(__('Continue Shopping')) ?></span> </a> <?php endif; ?> - <button type="submit" + <button type="button" name="update_cart_action" data-cart-empty="" value="empty_cart" diff --git a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js index 39bd07f0c73a0..69a2cec47181b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js @@ -15,13 +15,12 @@ define([ var items, i, reload; $(this.options.emptyCartButton).on('click', $.proxy(function (event) { - if (event.detail === 0) { - return; - } - $(this.options.emptyCartButton).attr('name', 'update_cart_action_temp'); $(this.options.updateCartActionContainer) .attr('name', 'update_cart_action').attr('value', 'empty_cart'); + if ($(this.options.emptyCartButton).parents('form').length > 0) { + $(this.options.emptyCartButton).parents('form').submit(); + } }, this)); items = $.find('[data-role="cart-item-qty"]'); From 1f597119af94c46d1a5d6a052393f91edca46904 Mon Sep 17 00:00:00 2001 From: Prabhat Rawat <prabhat.rawat763@webkul.com> Date: Sat, 7 Sep 2019 17:14:26 +0530 Subject: [PATCH 684/841] issue fix 23205 --- app/code/Magento/Vault/view/frontend/layout/customer_account.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Vault/view/frontend/layout/customer_account.xml b/app/code/Magento/Vault/view/frontend/layout/customer_account.xml index 73ce9247fef0a..05044da272e6d 100644 --- a/app/code/Magento/Vault/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Vault/view/frontend/layout/customer_account.xml @@ -11,7 +11,6 @@ <referenceBlock name="customer_account_navigation"> <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-my-credit-cards-link" - ifconfig="payment/braintree/active" > <arguments> <argument name="path" xsi:type="string">vault/cards/listaction</argument> From 5847d9f64dad423d83bf0c742d810783ca213639 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sat, 7 Sep 2019 19:13:14 +0700 Subject: [PATCH 685/841] Fix static test issue 24491 --- .../Magento/Checkout/view/frontend/web/js/shopping-cart.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js index 69a2cec47181b..b15599673095f 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js @@ -14,10 +14,11 @@ define([ _create: function () { var items, i, reload; - $(this.options.emptyCartButton).on('click', $.proxy(function (event) { + $(this.options.emptyCartButton).on('click', $.proxy(function () { $(this.options.emptyCartButton).attr('name', 'update_cart_action_temp'); $(this.options.updateCartActionContainer) .attr('name', 'update_cart_action').attr('value', 'empty_cart'); + if ($(this.options.emptyCartButton).parents('form').length > 0) { $(this.options.emptyCartButton).parents('form').submit(); } From 114ac4b39a5818df2f1c91c07fd9b27cc97de7b1 Mon Sep 17 00:00:00 2001 From: ashutosh <ashutosh045@webkul.com> Date: Sat, 7 Sep 2019 17:52:20 +0530 Subject: [PATCH 686/841] fixed issue #24410 --- .../Model/Product/TierPriceManagement.php | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php index f2da1e770279e..f078349c2a8f4 100644 --- a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php +++ b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php @@ -182,16 +182,19 @@ public function getList($sku, $customerGroupId) : $customerGroupId); $prices = []; - foreach ($product->getData('tier_price') as $price) { - if ((is_numeric($customerGroupId) && (int) $price['cust_group'] === (int) $customerGroupId) - || ($customerGroupId === 'all' && $price['all_groups']) - ) { - /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */ - $tierPrice = $this->priceFactory->create(); - $tierPrice->setValue($price[$priceKey]) - ->setQty($price['price_qty']) - ->setCustomerGroupId($cgi); - $prices[] = $tierPrice; + $tierPrices = $product->getData('tier_price'); + if ($tierPrices !== null) { + foreach ($tierPrices as $price) { + if ((is_numeric($customerGroupId) && (int) $price['cust_group'] === (int) $customerGroupId) + || ($customerGroupId === 'all' && $price['all_groups']) + ) { + /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */ + $tierPrice = $this->priceFactory->create(); + $tierPrice->setValue($price[$priceKey]) + ->setQty($price['price_qty']) + ->setCustomerGroupId($cgi); + $prices[] = $tierPrice; + } } } return $prices; From 41ef34feca73eea48f3dd1744bce529d36909c91 Mon Sep 17 00:00:00 2001 From: Pieter Hoste <hoste.pieter@gmail.com> Date: Sat, 7 Sep 2019 14:34:35 +0200 Subject: [PATCH 687/841] Fixes excluding JS files from bundles when minifying is enabled. --- app/code/Magento/Deploy/Service/Bundle.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Deploy/Service/Bundle.php b/app/code/Magento/Deploy/Service/Bundle.php index f16b93a185595..26e61624c219e 100644 --- a/app/code/Magento/Deploy/Service/Bundle.php +++ b/app/code/Magento/Deploy/Service/Bundle.php @@ -216,7 +216,7 @@ private function isExcluded($filePath, $area, $theme) $excludedFiles = $this->bundleConfig->getExcludedFiles($area, $theme); foreach ($excludedFiles as $excludedFileId) { $excludedFilePath = $this->prepareExcludePath($excludedFileId); - if ($excludedFilePath === $filePath) { + if ($excludedFilePath === $filePath || $excludedFilePath === str_replace('.min.js', '.js', $filePath)) { return true; } } From 47512517325f7d33b4f7f111d9d357b2d8961680 Mon Sep 17 00:00:00 2001 From: Pieter Hoste <hoste.pieter@gmail.com> Date: Thu, 5 Sep 2019 22:29:03 +0200 Subject: [PATCH 688/841] Excludes Magento_Tinymce3 scripts from the bundling, saves about 3.7 MB in the generated JS bundles. --- app/design/adminhtml/Magento/backend/etc/view.xml | 1 + app/design/frontend/Magento/blank/etc/view.xml | 1 + app/design/frontend/Magento/luma/etc/view.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/design/adminhtml/Magento/backend/etc/view.xml b/app/design/adminhtml/Magento/backend/etc/view.xml index 18c2d8f1b1722..05a5e1978c751 100644 --- a/app/design/adminhtml/Magento/backend/etc/view.xml +++ b/app/design/adminhtml/Magento/backend/etc/view.xml @@ -63,6 +63,7 @@ <item type="file">Lib::requirejs/text.js</item> <item type="file">Lib::date-format-normalizer.js</item> <item type="file">Lib::varien/js.js</item> + <item type="directory">Magento_Tinymce3::tiny_mce</item> <item type="directory">Lib::css</item> <item type="directory">Lib::lib</item> <item type="directory">Lib::prototype</item> diff --git a/app/design/frontend/Magento/blank/etc/view.xml b/app/design/frontend/Magento/blank/etc/view.xml index e742ce0a21cd1..0081a254eb3b6 100644 --- a/app/design/frontend/Magento/blank/etc/view.xml +++ b/app/design/frontend/Magento/blank/etc/view.xml @@ -292,6 +292,7 @@ <item type="directory">Magento_Ui::templates/grid</item> <item type="directory">Magento_Ui::templates/dynamic-rows</item> <item type="directory">Magento_Swagger::swagger-ui</item> + <item type="directory">Magento_Tinymce3::tiny_mce</item> <item type="directory">Lib::modernizr</item> <item type="directory">Lib::tiny_mce</item> <item type="directory">Lib::varien</item> diff --git a/app/design/frontend/Magento/luma/etc/view.xml b/app/design/frontend/Magento/luma/etc/view.xml index 7aa2e51481bd9..ff1d06204e51e 100644 --- a/app/design/frontend/Magento/luma/etc/view.xml +++ b/app/design/frontend/Magento/luma/etc/view.xml @@ -303,6 +303,7 @@ <item type="directory">Magento_Ui::templates/grid</item> <item type="directory">Magento_Ui::templates/dynamic-rows</item> <item type="directory">Magento_Swagger::swagger-ui</item> + <item type="directory">Magento_Tinymce3::tiny_mce</item> <item type="directory">Lib::modernizr</item> <item type="directory">Lib::tiny_mce</item> <item type="directory">Lib::varien</item> From 06a1aa1b1b5830ccc17c2ebb2fdc713892d22d59 Mon Sep 17 00:00:00 2001 From: rani-priya <rpnishy@gmail.com> Date: Sat, 7 Sep 2019 20:03:40 +0530 Subject: [PATCH 689/841] fixed Not able to update decimal quantity from cart details page. #24509 --- .../Checkout/view/frontend/templates/cart/item/default.phtml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml index 86c9a357af23c..d07f349265dfe 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml @@ -50,7 +50,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima <?php if (isset($_formatedOptionValue['full_view'])) :?> <?= $block->escapeHtml($_formatedOptionValue['full_view']) ?> <?php else :?> - <?= $block->escapeHtml($_formatedOptionValue['value'], ['span', 'a']) ?> + <?= $block->escapeHtml($_formatedOptionValue['value'], ['span']) ?> <?php endif; ?> </dd> <?php endforeach; ?> @@ -104,6 +104,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima value="<?= $block->escapeHtmlAttr($block->getQty()) ?>" type="number" size="4" + step="any" title="<?= $block->escapeHtmlAttr(__('Qty')) ?>" class="input-text qty" data-validate="{required:true,'validate-greater-than-zero':true}" From 2b3a44074286aa4ecf1eaf142a5dd23c22b5fa1e Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Sat, 7 Sep 2019 14:57:15 -0400 Subject: [PATCH 690/841] Add input validation for authorizenet_acceptjs payment method Fixes magento/graphql-ce#774 --- .../Model/AuthorizenetDataProvider.php | 25 +++- .../Customer/SetPaymentMethodTest.php | 107 ++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/AuthorizenetGraphQl/Model/AuthorizenetDataProvider.php b/app/code/Magento/AuthorizenetGraphQl/Model/AuthorizenetDataProvider.php index 207d21994308f..704f0af85da06 100644 --- a/app/code/Magento/AuthorizenetGraphQl/Model/AuthorizenetDataProvider.php +++ b/app/code/Magento/AuthorizenetGraphQl/Model/AuthorizenetDataProvider.php @@ -9,6 +9,7 @@ use Magento\QuoteGraphQl\Model\Cart\Payment\AdditionalDataProviderInterface; use Magento\Framework\Stdlib\ArrayManager; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; /** * SetPaymentMethod additional data provider model for Authorizenet payment method @@ -36,10 +37,32 @@ public function __construct( * * @param array $data * @return array + * @throws GraphQlInputException */ public function getData(array $data): array { - $additionalData = $this->arrayManager->get(static::PATH_ADDITIONAL_DATA, $data) ?? []; + if (!isset($data[self::PATH_ADDITIONAL_DATA])) { + throw new GraphQlInputException( + __('Required parameter "authorizenet_acceptjs" for "payment_method" is missing.') + ); + } + if (!isset($data[self::PATH_ADDITIONAL_DATA]['opaque_data_descriptor'])) { + throw new GraphQlInputException( + __('Required parameter "opaque_data_descriptor" for "authorizenet_acceptjs" is missing.') + ); + } + if (!isset($data[self::PATH_ADDITIONAL_DATA]['opaque_data_value'])) { + throw new GraphQlInputException( + __('Required parameter "opaque_data_value" for "authorizenet_acceptjs" is missing.') + ); + } + if (!isset($data[self::PATH_ADDITIONAL_DATA]['cc_last_4'])) { + throw new GraphQlInputException( + __('Required parameter "cc_last_4" for "authorizenet_acceptjs" is missing.') + ); + } + + $additionalData = $this->arrayManager->get(static::PATH_ADDITIONAL_DATA, $data); foreach ($additionalData as $key => $value) { $additionalData[$this->convertSnakeCaseToCamelCase($key)] = $value; unset($additionalData[$key]); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/AuthorizenetAcceptjs/Customer/SetPaymentMethodTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/AuthorizenetAcceptjs/Customer/SetPaymentMethodTest.php index ad454c67080e9..5f70cf4fd6687 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/AuthorizenetAcceptjs/Customer/SetPaymentMethodTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/AuthorizenetAcceptjs/Customer/SetPaymentMethodTest.php @@ -109,6 +109,113 @@ public function dataProviderTestPlaceOrder(): array ]; } + /** + * @magentoConfigFixture default_store carriers/flatrate/active 1 + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/active 1 + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login def_login + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key def_trans_key + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/public_client_key def_public_client_key + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key def_trans_signature_key + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_flatrate_shipping_method.php + * @dataProvider dataProviderSetPaymentInvalidInput + * @param \Closure $getMutationClosure + * @param string $expectedMessage + * @expectedException \Exception + */ + public function testSetPaymentInvalidInput(\Closure $getMutationClosure, string $expectedMessage) + { + $reservedOrderId = 'test_quote'; + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute($reservedOrderId); + + $setPaymentMutation = $getMutationClosure($maskedQuoteId); + + $this->expectExceptionMessage($expectedMessage); + $this->graphQlMutation($setPaymentMutation, [], '', $this->getHeaderMap()); + } + + /** + * Data provider for testSetPaymentInvalidInput + * + * @return array + */ + public function dataProviderSetPaymentInvalidInput(): array + { + return [ + [ + function (string $maskedQuoteId) { + return $this->getInvalidSetPaymentMutation($maskedQuoteId); + }, + 'Required parameter "authorizenet_acceptjs" for "payment_method" is missing.', + ], + [ + function (string $maskedQuoteId) { + return $this->getInvalidAcceptJsInput($maskedQuoteId); + }, + 'for "authorizenet_acceptjs" is missing.' + ], + ]; + } + + /** + * Get setPaymentMethodOnCart missing additional data property + * + * @param string $maskedQuoteId + * @return string + */ + private function getInvalidSetPaymentMutation(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + setPaymentMethodOnCart(input:{ + cart_id:"{$maskedQuoteId}" + payment_method:{ + code:"authorizenet_acceptjs" + } + }) { + cart { + selected_payment_method { + code + } + } + } +} +QUERY; + } + + /** + * Get setPaymentMethodOnCart missing require additional data properties + * + * @param string $maskedQuoteId + * @return string + */ + private function getInvalidAcceptJsInput(string $maskedQuoteId): string + { + return <<<QUERY +mutation { + setPaymentMethodOnCart(input:{ + cart_id:"{$maskedQuoteId}" + payment_method:{ + code:"authorizenet_acceptjs" + authorizenet_acceptjs: {} + } + }) { + cart { + selected_payment_method { + code + } + } + } +} +QUERY; + } + private function assertPlaceOrderResponse(array $response, string $reservedOrderId): void { self::assertArrayHasKey('placeOrder', $response); From deebd8591e9450f88e1f2f07bc90c544a5edda13 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Sun, 8 Sep 2019 11:34:38 +0700 Subject: [PATCH 691/841] Cover Magento Version Unit Test --- .../Test/Unit/Controller/Index/IndexTest.php | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php diff --git a/app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php b/app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php new file mode 100644 index 0000000000000..2ade1cd927afd --- /dev/null +++ b/app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Version\Test\Unit\Controller\Index; + +use Magento\Version\Controller\Index\Index as VersionIndex; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\ProductMetadataInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +/** + * Class \Magento\Version\Test\Unit\Controller\Index\IndexTest + */ +class IndexTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var VersionIndex + */ + private $model; + + /** + * @var Context + */ + private $context; + + /** + * @var ProductMetadataInterface + */ + private $productMetadata; + + /** + * @var ResponseInterface + */ + private $response; + + /** + * Prepare Mock Object + */ + protected function setUp() + { + $this->context = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->productMetadata = $this->getMockBuilder(ProductMetadataInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getName', 'getEdition', 'getVersion']) + ->getMock(); + + $this->response = $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setBody', 'sendResponse']) + ->getMock(); + + $this->context->expects($this->any()) + ->method('getResponse') + ->willReturn($this->response); + + $helper = new ObjectManager($this); + + $this->model = $helper->getObject( + 'Magento\Version\Controller\Index\Index', + [ + 'context' => $this->context, + 'productMetadata' => $this->productMetadata + ] + ); + } + + /** + * Test with Git Base version + */ + public function testExecuteWithGitBase() + { + $this->productMetadata->expects($this->any())->method('getVersion')->willReturn('dev-2.3'); + $this->assertNull($this->model->execute()); + } + + /** + * Test with Community Version + */ + public function testExecuteWithCommunityVersion() + { + $this->productMetadata->expects($this->any())->method('getVersion')->willReturn('2.3.3'); + $this->productMetadata->expects($this->any())->method('getEdition')->willReturn('Community'); + $this->productMetadata->expects($this->any())->method('getName')->willReturn('Magento'); + $this->response->expects($this->once())->method('setBody') + ->with('Magento/2.3 (Community)') + ->will($this->returnSelf()); + $this->model->execute(); + } +} From 4bf8bfdb7e148847c4c74c90bc90c5407b3ab8ae Mon Sep 17 00:00:00 2001 From: Rani Priya <rani.priya439@webkul.com> Date: Sun, 8 Sep 2019 12:26:19 +0530 Subject: [PATCH 692/841] fixed custom option file type issue --- .../Checkout/view/frontend/templates/cart/item/default.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml index d07f349265dfe..0267f68c5f81d 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml @@ -50,7 +50,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima <?php if (isset($_formatedOptionValue['full_view'])) :?> <?= $block->escapeHtml($_formatedOptionValue['full_view']) ?> <?php else :?> - <?= $block->escapeHtml($_formatedOptionValue['value'], ['span']) ?> + <?= $block->escapeHtml($_formatedOptionValue['value'], ['span', 'a']) ?> <?php endif; ?> </dd> <?php endforeach; ?> From d3e9fd4ce80b7fd7341c069f101bc298204d4430 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Sun, 8 Sep 2019 10:37:38 +0300 Subject: [PATCH 693/841] Covering the CouponCodeValidation class by Unit Tests --- .../Observer/CouponCodeValidationTest.php | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php diff --git a/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php b/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php new file mode 100644 index 0000000000000..6ad9db2d0ca7a --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php @@ -0,0 +1,174 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Test\Unit\Observer; + +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\DataObject; +use Magento\Framework\Event\Observer; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Quote; +use Magento\SalesRule\Api\Exception\CodeRequestLimitException; +use Magento\SalesRule\Model\Spi\CodeLimitManagerInterface; +use Magento\SalesRule\Observer\CouponCodeValidation; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * Class CouponCodeValidationTest + */ +class CouponCodeValidationTest extends TestCase +{ + /** + * @var CouponCodeValidation + */ + private $couponCodeValidation; + + /** + * @var PHPUnit_Framework_MockObject_MockObject|CodeLimitManagerInterface + */ + private $codeLimitManagerMock; + + /** + * @var PHPUnit_Framework_MockObject_MockObject|CartRepositoryInterface + */ + private $cartRepositoryMock; + + /** + * @var PHPUnit_Framework_MockObject_MockObject|SearchCriteriaBuilder + */ + private $searchCriteriaBuilderMock; + + /** + * @var PHPUnit_Framework_MockObject_MockObject|Observer + */ + private $observerMock; + + /** + * @var PHPUnit_Framework_MockObject_MockObject + */ + private $searchCriteriaMock; + + /** + * @var PHPUnit_Framework_MockObject_MockObject + */ + private $quoteMock; + + /** + * Set Up + */ + protected function setUp() + { + $this->codeLimitManagerMock = $this->createMock(CodeLimitManagerInterface::class); + $this->observerMock = $this->createMock(Observer::class); + $this->searchCriteriaMock = $this->getMockBuilder(SearchCriteria::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->cartRepositoryMock = $this->getMockBuilder(CartRepositoryInterface::class) + ->setMethods(['getItems']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->searchCriteriaBuilderMock = $this->getMockBuilder(SearchCriteriaBuilder::class) + ->setMethods(['addFilter', 'create']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->quoteMock = $this->createPartialMock(Quote::class, [ + 'getCouponCode', 'setCouponCode', 'getId' + ]); + + $this->couponCodeValidation = new CouponCodeValidation( + $this->codeLimitManagerMock, + $this->cartRepositoryMock, + $this->searchCriteriaBuilderMock + ); + } + + /** + * Testing the coupon code that haven't reached the request limit + */ + public function testCouponCodeNotReachedTheLimit() + { + $couponCode = 'AB123'; + $this->observerMock->expects($this->once())->method('getData')->with('quote') + ->willReturn($this->quoteMock); + $this->quoteMock->expects($this->once())->method('getCouponCode')->willReturn($couponCode); + $this->searchCriteriaBuilderMock->expects($this->once())->method('addFilter')->willReturnSelf(); + $this->searchCriteriaBuilderMock->expects($this->once())->method('create') + ->willReturn($this->searchCriteriaMock); + $this->quoteMock->expects($this->once())->method('getId')->willReturn(123); + $this->cartRepositoryMock->expects($this->any())->method('getList')->willReturnSelf(); + $this->cartRepositoryMock->expects($this->any())->method('getItems')->willReturn([]); + $this->codeLimitManagerMock->expects($this->once())->method('checkRequest')->with($couponCode); + $this->quoteMock->expects($this->never())->method('setCouponCode')->with(''); + + $this->couponCodeValidation->execute($this->observerMock); + } + + /** + * Testing with the changed coupon code + */ + public function testCouponCodeNotReachedTheLimitWithNewCouponCode() + { + $couponCode = 'AB123'; + $newCouponCode = 'AB234'; + + $this->observerMock->expects($this->once())->method('getData')->with('quote') + ->willReturn($this->quoteMock); + $this->quoteMock->expects($this->once())->method('getCouponCode')->willReturn($couponCode); + $this->searchCriteriaBuilderMock->expects($this->once())->method('addFilter')->willReturnSelf(); + $this->searchCriteriaBuilderMock->expects($this->once())->method('create') + ->willReturn($this->searchCriteriaMock); + $this->quoteMock->expects($this->once())->method('getId')->willReturn(123); + $this->cartRepositoryMock->expects($this->any())->method('getList')->willReturnSelf(); + $this->cartRepositoryMock->expects($this->any())->method('getItems') + ->willReturn([new DataObject(['coupon_code' => $newCouponCode])]); + $this->codeLimitManagerMock->expects($this->once())->method('checkRequest')->with($couponCode); + $this->quoteMock->expects($this->never())->method('setCouponCode')->with(''); + + $this->couponCodeValidation->execute($this->observerMock); + } + + /** + * Testing the coupon code that reached the request limit + * + * @expectedException \Magento\SalesRule\Api\Exception\CodeRequestLimitException + * @expectedExceptionMessage Too many coupon code requests, please try again later. + */ + public function testReachingLimitForCouponCode() + { + $couponCode = 'AB123'; + $this->observerMock->expects($this->once())->method('getData')->with('quote') + ->willReturn($this->quoteMock); + $this->quoteMock->expects($this->once())->method('getCouponCode')->willReturn($couponCode); + $this->searchCriteriaBuilderMock->expects($this->once())->method('addFilter')->willReturnSelf(); + $this->searchCriteriaBuilderMock->expects($this->once())->method('create') + ->willReturn($this->searchCriteriaMock); + $this->quoteMock->expects($this->once())->method('getId')->willReturn(123); + $this->cartRepositoryMock->expects($this->any())->method('getList')->willReturnSelf(); + $this->cartRepositoryMock->expects($this->any())->method('getItems')->willReturn([]); + $this->codeLimitManagerMock->expects($this->once())->method('checkRequest')->with($couponCode) + ->willThrowException( + new CodeRequestLimitException(__('Too many coupon code requests, please try again later.')) + ); + $this->quoteMock->expects($this->once())->method('setCouponCode')->with(''); + + $this->couponCodeValidation->execute($this->observerMock); + } + + /** + * Testing the quote that doesn't have a coupon code set + */ + public function testQuoteWithNoCouponCode() + { + $couponCode = null; + $this->observerMock->expects($this->once())->method('getData')->with('quote') + ->willReturn($this->quoteMock); + $this->quoteMock->expects($this->once())->method('getCouponCode')->willReturn($couponCode); + $this->quoteMock->expects($this->never())->method('getId')->willReturn(123); + $this->quoteMock->expects($this->never())->method('setCouponCode')->with(''); + + $this->couponCodeValidation->execute($this->observerMock); + } +} From 80fc9b82e8fe2cd93208303095d005b30d2e9921 Mon Sep 17 00:00:00 2001 From: Yaroslav Rogoza <enarc@atwix.com> Date: Sun, 8 Sep 2019 10:12:35 +0200 Subject: [PATCH 694/841] Comments adjustments --- .../Magento/Version/Test/Unit/Controller/Index/IndexTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php b/app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php index 2ade1cd927afd..6f8daa9d8008d 100644 --- a/app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php +++ b/app/code/Magento/Version/Test/Unit/Controller/Index/IndexTest.php @@ -39,7 +39,7 @@ class IndexTest extends \PHPUnit\Framework\TestCase private $response; /** - * Prepare Mock Object + * Prepare test preconditions */ protected function setUp() { From bebbc6b7b619261d6b1c1f450f30915464e850d2 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Sun, 8 Sep 2019 11:46:50 +0300 Subject: [PATCH 695/841] Fixing static tests --- .../Test/Unit/Observer/CouponCodeValidationTest.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php b/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php index 6ad9db2d0ca7a..e9ff0324f0187 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php @@ -74,9 +74,13 @@ protected function setUp() $this->searchCriteriaBuilderMock = $this->getMockBuilder(SearchCriteriaBuilder::class) ->setMethods(['addFilter', 'create']) ->disableOriginalConstructor()->getMockForAbstractClass(); - $this->quoteMock = $this->createPartialMock(Quote::class, [ - 'getCouponCode', 'setCouponCode', 'getId' - ]); + $this->quoteMock = $this->createPartialMock(Quote::class, + [ + 'getCouponCode', + 'setCouponCode', + 'getId' + ] + ); $this->couponCodeValidation = new CouponCodeValidation( $this->codeLimitManagerMock, From 809a0c026ad21fb8bf7cf2f118dd8697bb6aab57 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Sun, 8 Sep 2019 12:12:20 +0300 Subject: [PATCH 696/841] Fixing Static Tests --- .../Test/Unit/Observer/CouponCodeValidationTest.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php b/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php index e9ff0324f0187..2fe2069b0c8b1 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Observer/CouponCodeValidationTest.php @@ -74,12 +74,9 @@ protected function setUp() $this->searchCriteriaBuilderMock = $this->getMockBuilder(SearchCriteriaBuilder::class) ->setMethods(['addFilter', 'create']) ->disableOriginalConstructor()->getMockForAbstractClass(); - $this->quoteMock = $this->createPartialMock(Quote::class, - [ - 'getCouponCode', - 'setCouponCode', - 'getId' - ] + $this->quoteMock = $this->createPartialMock( + Quote::class, + ['getCouponCode', 'setCouponCode', 'getId'] ); $this->couponCodeValidation = new CouponCodeValidation( From 28596decdd54359e90a905b5976a77209d225456 Mon Sep 17 00:00:00 2001 From: Patrick McLain <pat@pmclain.com> Date: Sat, 7 Sep 2019 16:27:03 -0400 Subject: [PATCH 697/841] Deprecate setPaymentMethodAndPlaceOrderMutation Fixes magento/graphql-ce#875 --- .../Resolver/SetPaymentAndPlaceOrder.php | 6 ++- .../Magento/QuoteGraphQl/etc/schema.graphqls | 2 +- .../SetPaymentMethodAndPlaceOrderTest.php | 43 ++++++++++++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php index 0efbde5d6b218..03e1e6ffe822d 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentAndPlaceOrder.php @@ -20,7 +20,11 @@ use Magento\Sales\Api\OrderRepositoryInterface; /** - * @inheritdoc + * Resolver for setting payment method and placing order + * + * @deprecated Should use setPaymentMethodOnCart and placeOrder mutations in single request. + * @see \Magento\QuoteGraphQl\Model\Resolver\SetPaymentMethodOnCart + * @see \Magento\QuoteGraphQl\Model\Resolver\PlaceOrder */ class SetPaymentAndPlaceOrder implements ResolverInterface { diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 08bb78ba776c4..816a1f40d953a 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -18,7 +18,7 @@ type Mutation { setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetShippingMethodsOnCart") setPaymentMethodOnCart(input: SetPaymentMethodOnCartInput): SetPaymentMethodOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentMethodOnCart") setGuestEmailOnCart(input: SetGuestEmailOnCartInput): SetGuestEmailOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetGuestEmailOnCart") - setPaymentMethodAndPlaceOrder(input: SetPaymentMethodAndPlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentAndPlaceOrder") + setPaymentMethodAndPlaceOrder(input: SetPaymentMethodAndPlaceOrderInput): PlaceOrderOutput @deprecated(reason: "Should use setPaymentMethodOnCart and placeOrder mutations in single request.") @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentAndPlaceOrder") placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodAndPlaceOrderTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodAndPlaceOrderTest.php index 12fb356904224..192c10a67aa6b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodAndPlaceOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodAndPlaceOrderTest.php @@ -78,9 +78,14 @@ public function testSetPaymentOnCartWithSimpleProduct() $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); - self::assertArrayHasKey('setPaymentMethodAndPlaceOrder', $response); - self::assertArrayHasKey('order', $response['setPaymentMethodAndPlaceOrder']); - self::assertArrayHasKey('order_id', $response['setPaymentMethodAndPlaceOrder']['order']); + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']); + self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); + + self::assertArrayHasKey('order', $response['placeOrder']); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); } /** @@ -116,9 +121,14 @@ public function testSetPaymentOnCartWithVirtualProduct() $query = $this->getQuery($maskedQuoteId, $methodCode); $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); - self::assertArrayHasKey('setPaymentMethodAndPlaceOrder', $response); - self::assertArrayHasKey('order', $response['setPaymentMethodAndPlaceOrder']); - self::assertArrayHasKey('order_id', $response['setPaymentMethodAndPlaceOrder']['order']); + self::assertArrayHasKey('setPaymentMethodOnCart', $response); + self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']); + self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']); + self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']); + self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']); + + self::assertArrayHasKey('order', $response['placeOrder']); + self::assertArrayHasKey('order_id', $response['placeOrder']['order']); } /** @@ -224,12 +234,25 @@ private function getQuery( ) : string { return <<<QUERY mutation { - setPaymentMethodAndPlaceOrder(input: { - cart_id: "$maskedQuoteId" + setPaymentMethodOnCart( + input: { + cart_id: "{$maskedQuoteId}" payment_method: { - code: "$methodCode" + code: "{$methodCode}" + } + } + ) { + cart { + selected_payment_method { + code } - }) { + } + } + placeOrder( + input: { + cart_id: "{$maskedQuoteId}" + } + ) { order { order_id } From a7bec1ae86cbc65f7ecef0133272054ed742265a Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sun, 8 Sep 2019 19:14:44 -0500 Subject: [PATCH 698/841] MC-19904: Start/End Date time is changed after event saving in event edit page --- .../Framework/Stdlib/DateTime/Timezone.php | 26 +++--- .../Test/Unit/DateTime/TimezoneTest.php | 87 +++++++++++++++---- 2 files changed, 88 insertions(+), 25 deletions(-) diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php index 014854ddd584f..118a3e053bd79 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php @@ -195,16 +195,16 @@ public function date($date = null, $locale = null, $useTimezone = true, $include */ public function scopeDate($scope = null, $date = null, $includeTime = false) { - $timezone = $this->_scopeConfig->getValue($this->getDefaultTimezonePath(), $this->_scopeType, $scope); + $timezone = new \DateTimeZone( + $this->_scopeConfig->getValue($this->getDefaultTimezonePath(), $this->_scopeType, $scope) + ); switch (true) { case (empty($date)): - $date = new \DateTime('now', new \DateTimeZone($timezone)); + $date = new \DateTime('now', $timezone); break; case ($date instanceof \DateTime): - $date = $date->setTimezone(new \DateTimeZone($timezone)); - break; case ($date instanceof \DateTimeImmutable): - $date = new \DateTime($date->format('Y-m-d H:i:s'), $date->getTimezone()); + $date = $date->setTimezone($timezone); break; case (!is_numeric($date)): $timeType = $includeTime ? \IntlDateFormatter::SHORT : \IntlDateFormatter::NONE; @@ -212,14 +212,20 @@ public function scopeDate($scope = null, $date = null, $includeTime = false) $this->_localeResolver->getLocale(), \IntlDateFormatter::SHORT, $timeType, - new \DateTimeZone($timezone) + $timezone ); - $date = $formatter->parse($date) ?: (new \DateTime($date))->getTimestamp(); - $date = (new \DateTime(null, new \DateTimeZone($timezone)))->setTimestamp($date); + $timestamp = $formatter->parse($date); + $date = $timestamp + ? (new \DateTime('@' . $timestamp))->setTimezone($timezone) + : new \DateTime($date, $timezone); + break; + case (is_numeric($date)): + $date = new \DateTime('@' . $date); + $date = $date->setTimezone($timezone); break; default: - $date = new \DateTime(is_numeric($date) ? '@' . $date : $date); - $date->setTimezone(new \DateTimeZone($timezone)); + $date = new \DateTime($date, $timezone); + break; } if (!$includeTime) { diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php index 3d7d14a394629..53980e574c267 100644 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php @@ -22,6 +22,16 @@ class TimezoneTest extends \PHPUnit\Framework\TestCase */ private $defaultTimeZone; + /** + * @var string + */ + private $scopeType; + + /** + * @var string + */ + private $defaultTimezonePath; + /** * @var ObjectManager */ @@ -49,6 +59,8 @@ protected function setUp() { $this->defaultTimeZone = date_default_timezone_get(); date_default_timezone_set('UTC'); + $this->scopeType = 'store'; + $this->defaultTimezonePath = 'default/timezone/path'; $this->objectManager = new ObjectManager($this); $this->scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class)->getMock(); @@ -86,9 +98,10 @@ public function testDateIncludeTime($date, $locale, $includeTime, $expectedTimes /** * DataProvider for testDateIncludeTime + * * @return array */ - public function dateIncludeTimeDataProvider() + public function dateIncludeTimeDataProvider(): array { return [ 'Parse d/m/y date without time' => [ @@ -133,9 +146,10 @@ public function testConvertConfigTimeToUtc($date, $configuredTimezone, $expected /** * Data provider for testConvertConfigTimeToUtc + * * @return array */ - public function getConvertConfigTimeToUtcFixtures() + public function getConvertConfigTimeToUtcFixtures(): array { return [ 'string' => [ @@ -181,9 +195,10 @@ public function testDate() /** * DataProvider for testDate + * * @return array */ - private function getDateFixtures() + private function getDateFixtures(): array { return [ 'now_datetime_utc' => [ @@ -239,29 +254,71 @@ private function getTimezone() return new Timezone( $this->scopeResolver, $this->localeResolver, - $this->getMockBuilder(DateTime::class)->getMock(), + $this->createMock(DateTime::class), $this->scopeConfig, - '', - '' + $this->scopeType, + $this->defaultTimezonePath ); } /** * @param string $configuredTimezone + * @param string|null $scope */ - private function scopeConfigWillReturnConfiguredTimezone($configuredTimezone) + private function scopeConfigWillReturnConfiguredTimezone(string $configuredTimezone, string $scope = null) { - $this->scopeConfig->method('getValue')->with('', '', null)->willReturn($configuredTimezone); + $this->scopeConfig->expects($this->atLeastOnce()) + ->method('getValue') + ->with($this->defaultTimezonePath, $this->scopeType, $scope) + ->willReturn($configuredTimezone); } - public function testCheckIfScopeDateSetsTimeZone() + /** + * @dataProvider scopeDateDataProvider + * @param \DateTimeInterface|string|int $date + * @param string $timezone + * @param string $locale + * @param string $expectedDate + */ + public function testScopeDate($date, string $timezone, string $locale, string $expectedDate) { - $scopeDate = new \DateTime('now', new \DateTimeZone('America/Vancouver')); - $this->scopeConfig->method('getValue')->willReturn('America/Vancouver'); + $scopeCode = 'test'; - $this->assertEquals( - $scopeDate->getTimezone(), - $this->getTimezone()->scopeDate(0, $scopeDate->getTimestamp())->getTimezone() - ); + $this->scopeConfigWillReturnConfiguredTimezone($timezone, $scopeCode); + $this->localeResolver->method('getLocale') + ->willReturn($locale); + + $scopeDate = $this->getTimezone()->scopeDate($scopeCode, $date, true); + $this->assertEquals($expectedDate, $scopeDate->format('Y-m-d H:i:s')); + $this->assertEquals($timezone, $scopeDate->getTimezone()->getName()); + } + + /** + * @return array + */ + public function scopeDateDataProvider(): array + { + $utcTz = new \DateTimeZone('UTC'); + + return [ + ['2018-10-20 00:00:00', 'UTC', 'en_US', '2018-10-20 00:00:00'], + ['2018-10-20 00:00:00', 'America/Los_Angeles', 'en_US', '2018-10-20 00:00:00'], + ['2018-10-20 00:00:00', 'Asia/Qatar', 'en_US', '2018-10-20 00:00:00'], + ['10/20/18 00:00', 'UTC', 'en_US', '2018-10-20 00:00:00'], + ['10/20/18 00:00', 'America/Los_Angeles', 'en_US', '2018-10-20 00:00:00'], + ['10/20/18 00:00', 'Asia/Qatar', 'en_US', '2018-10-20 00:00:00'], + ['20/10/18 00:00', 'UTC', 'fr_FR', '2018-10-20 00:00:00'], + ['20/10/18 00:00', 'America/Los_Angeles', 'fr_FR', '2018-10-20 00:00:00'], + ['20/10/18 00:00', 'Asia/Qatar', 'fr_FR', '2018-10-20 00:00:00'], + [1539993600, 'UTC', 'en_US', '2018-10-20 00:00:00'], + [1539993600, 'America/Los_Angeles', 'en_US', '2018-10-19 17:00:00'], + [1539993600, 'Asia/Qatar', 'en_US', '2018-10-20 03:00:00'], + [new \DateTime('2018-10-20', $utcTz), 'UTC', 'en_US', '2018-10-20 00:00:00'], + [new \DateTime('2018-10-20', $utcTz), 'America/Los_Angeles', 'en_US', '2018-10-19 17:00:00'], + [new \DateTime('2018-10-20', $utcTz), 'Asia/Qatar', 'en_US', '2018-10-20 03:00:00'], + [new \DateTimeImmutable('2018-10-20', $utcTz), 'UTC', 'en_US', '2018-10-20 00:00:00'], + [new \DateTimeImmutable('2018-10-20', $utcTz), 'America/Los_Angeles', 'en_US', '2018-10-19 17:00:00'], + [new \DateTimeImmutable('2018-10-20', $utcTz), 'Asia/Qatar', 'en_US', '2018-10-20 03:00:00'], + ]; } } From 1da4f2ac4985ab6d1d348e93d6fa8670888a2f5c Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Mon, 9 Sep 2019 13:20:47 +0700 Subject: [PATCH 699/841] Resolve API "V1/attributeMetadata/customerAddress/attribute/prefix" and "V1/attributeMetadata/customerAddress/attribute/suffix" can not get options issue24518 --- .../Model/AttributeMetadataConverter.php | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Customer/Model/AttributeMetadataConverter.php b/app/code/Magento/Customer/Model/AttributeMetadataConverter.php index 44d104659e069..0407aebf9d670 100644 --- a/app/code/Magento/Customer/Model/AttributeMetadataConverter.php +++ b/app/code/Magento/Customer/Model/AttributeMetadataConverter.php @@ -3,18 +3,36 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Customer\Model; use Magento\Customer\Api\Data\OptionInterfaceFactory; use Magento\Customer\Api\Data\ValidationRuleInterfaceFactory; use Magento\Customer\Api\Data\AttributeMetadataInterfaceFactory; use Magento\Eav\Api\Data\AttributeDefaultValueInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; /** * Converter for AttributeMetadata */ class AttributeMetadataConverter { + /** + * Attribute Code get options from system config + * + * @var array + */ + private const ATTRIBUTE_CODE_LIST_FROM_SYSTEM_CONFIG = ['prefix', 'suffix']; + + /** + * XML Path to get address config + * + * @var string + */ + private const XML_CUSTOMER_ADDRESS = 'customer/address/'; + /** * @var OptionInterfaceFactory */ @@ -35,6 +53,11 @@ class AttributeMetadataConverter */ protected $dataObjectHelper; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + /** * Initialize the Converter * @@ -42,17 +65,20 @@ class AttributeMetadataConverter * @param ValidationRuleInterfaceFactory $validationRuleFactory * @param AttributeMetadataInterfaceFactory $attributeMetadataFactory * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + * @param ScopeConfigInterface $scopeConfig */ public function __construct( OptionInterfaceFactory $optionFactory, ValidationRuleInterfaceFactory $validationRuleFactory, AttributeMetadataInterfaceFactory $attributeMetadataFactory, - \Magento\Framework\Api\DataObjectHelper $dataObjectHelper + \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, + ScopeConfigInterface $scopeConfig = null ) { $this->optionFactory = $optionFactory; $this->validationRuleFactory = $validationRuleFactory; $this->attributeMetadataFactory = $attributeMetadataFactory; $this->dataObjectHelper = $dataObjectHelper; + $this->scopeConfig = $scopeConfig ?? ObjectManager::getInstance()->get(ScopeConfigInterface::class); } /** @@ -64,28 +90,34 @@ public function __construct( public function createMetadataAttribute($attribute) { $options = []; - if ($attribute->usesSource()) { - foreach ($attribute->getSource()->getAllOptions() as $option) { - $optionDataObject = $this->optionFactory->create(); - if (!is_array($option['value'])) { - $optionDataObject->setValue($option['value']); - } else { - $optionArray = []; - foreach ($option['value'] as $optionArrayValues) { - $optionObject = $this->optionFactory->create(); - $this->dataObjectHelper->populateWithArray( - $optionObject, - $optionArrayValues, - \Magento\Customer\Api\Data\OptionInterface::class - ); - $optionArray[] = $optionObject; + + if (in_array($attribute->getAttributeCode(), self::ATTRIBUTE_CODE_LIST_FROM_SYSTEM_CONFIG)) { + $options = $this->getOptionFromConfig($attribute->getAttributeCode()); + } else { + if ($attribute->usesSource()) { + foreach ($attribute->getSource()->getAllOptions() as $option) { + $optionDataObject = $this->optionFactory->create(); + if (!is_array($option['value'])) { + $optionDataObject->setValue($option['value']); + } else { + $optionArray = []; + foreach ($option['value'] as $optionArrayValues) { + $optionObject = $this->optionFactory->create(); + $this->dataObjectHelper->populateWithArray( + $optionObject, + $optionArrayValues, + \Magento\Customer\Api\Data\OptionInterface::class + ); + $optionArray[] = $optionObject; + } + $optionDataObject->setOptions($optionArray); } - $optionDataObject->setOptions($optionArray); + $optionDataObject->setLabel($option['label']); + $options[] = $optionDataObject; } - $optionDataObject->setLabel($option['label']); - $options[] = $optionDataObject; } } + $validationRules = []; foreach ((array)$attribute->getValidateRules() as $name => $value) { $validationRule = $this->validationRuleFactory->create() @@ -122,4 +154,26 @@ public function createMetadataAttribute($attribute) ->setIsFilterableInGrid($attribute->getIsFilterableInGrid()) ->setIsSearchableInGrid($attribute->getIsSearchableInGrid()); } + + /** + * Get option from System Config instead of Use Source (Prefix, Suffix) + * + * @param string $attributeCode + * @return \Magento\Customer\Api\Data\OptionInterface[] + */ + private function getOptionFromConfig($attributeCode) + { + $result = []; + $value = $this->scopeConfig->getValue(self::XML_CUSTOMER_ADDRESS . $attributeCode . '_options'); + if ($value) { + $optionArray = explode(';', $value); + foreach ($optionArray as $value) { + $optionObject = $this->optionFactory->create(); + $optionObject->setLabel($value); + $optionObject->setValue($value); + $result[] = $optionObject; + } + } + return $result; + } } From 08e446e28f4f9949eb9a907e74c4d544578ab787 Mon Sep 17 00:00:00 2001 From: Yuliya Labudova <Yuliya_Labudova@epam.com> Date: Mon, 9 Sep 2019 10:31:40 +0300 Subject: [PATCH 700/841] MAGETWO-94565: Catalog product collection filters produce errors and cause inconsistent behaviour - Fix static tests. --- .../Catalog/Model/ResourceModel/Product/CollectionTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php index d3970ad403ffd..de0e881474cf0 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php @@ -15,6 +15,8 @@ /** * Collection test + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CollectionTest extends \PHPUnit\Framework\TestCase { From 650c215872275e6ff392a988f5b19e8aae616928 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Mon, 9 Sep 2019 12:14:59 +0300 Subject: [PATCH 701/841] magento/magento2#24380: Integration tests fix. --- .../Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php index 4c653ab9ae33f..8d6c2625daadc 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/Controller/Cart/UpdateItemQtyTest.php @@ -10,6 +10,7 @@ use Magento\Catalog\Model\Product; use Magento\Checkout\Model\Session; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\Request\Http as HttpRequest; use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Serialize\Serializer\Json; @@ -81,6 +82,7 @@ public function testExecute($requestQuantity, $expectedResponse) ]; } + $this->getRequest()->setMethod(HttpRequest::METHOD_POST); $this->getRequest()->setPostValue($request); $this->dispatch('checkout/cart/updateItemQty'); $response = $this->getResponse()->getBody(); From a356f94dd8004db75b7b57319097cb857fe2c2b9 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 9 Sep 2019 08:26:37 -0500 Subject: [PATCH 702/841] MC-19702: Add input_type to customAttributeMetadata query - fix test to avoid using hard coded values for attribute options --- .../Catalog/ProductAttributeOptionsTest.php | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php index 53e09bf590e3c..517a1c966b04d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductAttributeOptionsTest.php @@ -8,6 +8,7 @@ namespace Magento\GraphQl\Catalog; use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Eav\Api\Data\AttributeOptionInterface; class ProductAttributeOptionsTest extends GraphQlAbstract { @@ -18,6 +19,17 @@ class ProductAttributeOptionsTest extends GraphQlAbstract */ public function testCustomAttributeMetadataOptions() { + /** @var \Magento\Eav\Model\Config $eavConfig */ + $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $attribute = $eavConfig->getAttribute('catalog_product', 'dropdown_attribute'); + /** @var AttributeOptionInterface[] $options */ + $options = $attribute->getOptions(); + array_shift($options); + $optionValues = []; + // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall + for ($i = 0; $i < count($options); $i++) { + $optionValues[] = $options[$i]->getValue(); + } $query = <<<QUERY { @@ -69,15 +81,15 @@ public function testCustomAttributeMetadataOptions() [ [ 'label' => 'Option 1', - 'value' => '10' + 'value' => $optionValues[0] ], [ 'label' => 'Option 2', - 'value' => '11' + 'value' => $optionValues[1] ], [ 'label' => 'Option 3', - 'value' => '12' + 'value' => $optionValues[2] ] ] ]; From f006daad2981d49026fd392ecce861664d2b7aa3 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 9 Sep 2019 08:28:19 -0500 Subject: [PATCH 703/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix failing tests --- .../GraphQl/Catalog/ProductSearchTest.php | 62 ++----------------- .../GraphQl/Catalog/ProductViewTest.php | 10 ++- .../GraphQl/Swatches/ProductSearchTest.php | 2 +- 3 files changed, 13 insertions(+), 61 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index cef036a31e6b7..2e3e67c65ca46 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -106,7 +106,7 @@ public function testFilterLn() * @magentoApiDataFixture Magento/Catalog/_files/configurable_products_with_custom_attribute_layered_navigation.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testLayeredNavigationWithConfigurableChildrenOutOfStock() + public function testLayeredNavigationForConfigurableProducts() { CacheCleaner::cleanAll(); $attributeCode = 'test_configurable'; @@ -704,59 +704,6 @@ public function testFilterByCategoryIdAndCustomAttribute() ); } } - /** - * - * @return string - */ - private function getQueryProductsWithCustomAttribute($attributeCode, $optionValue) : string - { - return <<<QUERY -{ - products(filter:{ - $attributeCode: {eq: "{$optionValue}"} - } - pageSize: 3 - currentPage: 1 - ) - { - total_count - items - { - name - sku - } - page_info{ - current_page - page_size - total_pages - } - filters{ - name - request_var - filter_items_count - filter_items{ - label - items_count - value_string - __typename - } - - - } - aggregations{ - attribute_code - count - label - options{ - label - value - } - } - - } -} -QUERY; - } /** * Get array with expected data for layered navigation filters @@ -849,7 +796,7 @@ private function assertFilters($response, $expectedFilters, $message = '') * @magentoApiDataFixture Magento/Catalog/_files/multiple_products.php * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testFilterProductsWithinSpecificPriceRangeSortedByNameDesc() + public function testFilterWithinSpecificPriceRangeSortedByNameDesc() { $query = <<<QUERY @@ -1355,10 +1302,11 @@ public function testSearchAndSortByRelevance() $this->assertEquals(4, $response['products']['total_count']); $this->assertNotEmpty($response['products']['filters'],'Filters should have the Category layer'); $this->assertEquals('Colorful Category', $response['products']['filters'][0]['filter_items'][0]['label']); - $productsInResponse = ['Blue briefs', 'ocean blue Shoes', 'Navy Striped Shoes','Grey shorts']; + $productsInResponse = ['ocean blue Shoes', 'Blue briefs', 'Navy Striped Shoes','Grey shorts']; for ($i = 0; $i < count($response['products']['items']); $i++) { $this->assertEquals($productsInResponse[$i], $response['products']['items'][$i]['name']); } + $this->assertCount(2, $response['products']['aggregations']); } /** @@ -1521,7 +1469,7 @@ public function testProductBasicFullTextSearchQuery() * @magentoApiDataFixture Magento/Catalog/_files/category.php * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products_2.php */ - public function testFilterProductsWithinASpecificPriceRangeSortedByPriceDESC() + public function testFilterWithinASpecificPriceRangeSortedByPriceDESC() { /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php index 378b87fb9591f..5685fcdb25877 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php @@ -981,7 +981,7 @@ public function testProductInAllAnchoredCategories() { $query = <<<QUERY { - products(filter: {sku: {like: "12345%"}}) + products(filter: {sku: {in: ["12345"]}}) { items { @@ -1031,7 +1031,7 @@ public function testProductWithNonAnchoredParentCategory() { $query = <<<QUERY { - products(filter: {sku: {like: "12345%"}}) + products(filter: {sku: {in: ["12345"]}}) { items { @@ -1085,7 +1085,11 @@ public function testProductInNonAnchoredSubCategories() { $query = <<<QUERY { - products(filter: {sku: {like: "12345%"}}) + products(filter: + { + sku: {in:["12345"]} + } + ) { items { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Swatches/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Swatches/ProductSearchTest.php index f0397c51c4660..8ba8b534cfe5c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Swatches/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Swatches/ProductSearchTest.php @@ -30,7 +30,7 @@ public function testFilterLn() products ( filter:{ sku:{ - like:"%simple%" + in:["simple1", "simple2", "simple3"] } } pageSize: 4 From 8eef878ba3e25e92f9b58c11bd4058baaff451df Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Mon, 9 Sep 2019 09:39:20 -0500 Subject: [PATCH 704/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Fix sorting issue - Use interface for aggregation options --- .../Product/SearchCriteriaBuilder.php | 16 ++----- .../Model/AggregationOptionTypeResolver.php | 29 ++++++++++++ ...AggregationOptionTypeResolverComposite.php | 45 +++++++++++++++++++ .../Products/DataProvider/ProductSearch.php | 12 ++--- .../Magento/CatalogGraphQl/etc/graphql/di.xml | 7 +++ .../CatalogGraphQl/etc/schema.graphqls | 8 +++- .../Adapter/Mysql/Filter/Preprocessor.php | 5 ++- .../Search/Adapter/Mysql/TemporaryStorage.php | 2 +- 8 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolver.php create mode 100644 app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolverComposite.php diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index af6ed85196cf8..810e9172d0973 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -97,8 +97,9 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte if (!empty($args['search'])) { $this->addFilter($searchCriteria, 'search_term', $args['search']); } - - $this->addDefaultSortOrder($searchCriteria); + if (!$searchCriteria->getSortOrders()) { + $this->addDefaultSortOrder($searchCriteria); + } $this->addVisibilityFilter($searchCriteria, !empty($args['search']), !empty($args['filter'])); $searchCriteria->setCurrentPage($args['currentPage']); @@ -170,21 +171,12 @@ private function addFilter(SearchCriteriaInterface $searchCriteria, string $fiel */ private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria): void { - $sortOrders = $searchCriteria->getSortOrders() ?? []; - foreach ($sortOrders as $sortOrder) { - // Relevance order is already specified - if ($sortOrder->getField() === 'relevance') { - return; - } - } $defaultSortOrder = $this->sortOrderBuilder ->setField('relevance') ->setDirection(SortOrder::SORT_DESC) ->create(); - $sortOrders[] = $defaultSortOrder; - - $searchCriteria->setSortOrders($sortOrders); + $searchCriteria->setSortOrders([$defaultSortOrder]); } /** diff --git a/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolver.php b/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolver.php new file mode 100644 index 0000000000000..3a532a1a6c760 --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolver.php @@ -0,0 +1,29 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model; + +use \Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; + +/** + * Resolver for aggregation option type. + */ +class AggregationOptionTypeResolver implements TypeResolverInterface +{ + /** + * @inheritdoc + */ + public function resolveType(array $data) : string + { + return isset($data['value']) + && isset($data['label']) + && isset($data['count']) + && count($data) == 3 + ? 'AggregationOption' + : ''; + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolverComposite.php b/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolverComposite.php new file mode 100644 index 0000000000000..eb0127c63784d --- /dev/null +++ b/app/code/Magento/CatalogGraphQl/Model/AggregationOptionTypeResolverComposite.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\Model; + +use \Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; + +/** + * Composite resolver for aggregation options. + */ +class AggregationOptionTypeResolverComposite implements TypeResolverInterface +{ + /** + * @var TypeResolverInterface[] + */ + private $typeResolvers; + + /** + * @param array $typeResolvers + */ + public function __construct(array $typeResolvers = []) + { + $this->typeResolvers = $typeResolvers; + } + + /** + * @inheritdoc + */ + public function resolveType(array $data) : string + { + /** @var TypeResolverInterface $typeResolver */ + foreach ($this->typeResolvers as $typeResolver) { + $resolvedType = $typeResolver->resolveType($data); + if ($resolvedType) { + return $resolvedType; + } + } + throw new GraphQlInputException(__('Cannot resolve aggregation option type')); + } +} diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php index 8c5fe42c730f6..661c5874614b5 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php @@ -79,7 +79,7 @@ public function __construct( */ public function getList( SearchCriteriaInterface $searchCriteria, - SearchResultsInterface $searchResult, + SearchResultInterface $searchResult, array $attributes = [] ): SearchResultsInterface { /** @var Collection $collection */ @@ -92,11 +92,11 @@ public function getList( $collection->load(); $this->collectionPostProcessor->process($collection, $attributes); - $searchResult = $this->searchResultsFactory->create(); - $searchResult->setSearchCriteria($searchCriteria); - $searchResult->setItems($collection->getItems()); - $searchResult->setTotalCount($collection->getSize()); - return $searchResult; + $searchResults = $this->searchResultsFactory->create(); + $searchResults->setSearchCriteria($searchCriteria); + $searchResults->setItems($collection->getItems()); + $searchResults->setTotalCount($collection->getSize()); + return $searchResults; } /** diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index ea2704f1a4ee5..fe3413dc3b218 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -28,6 +28,13 @@ </argument> </arguments> </type> + <type name="Magento\CatalogGraphQl\Model\AggregationOptionTypeResolverComposite"> + <arguments> + <argument name="typeResolvers" xsi:type="array"> + <item name="aggregation_option" xsi:type="object">Magento\CatalogGraphQl\Model\AggregationOptionTypeResolver</item> + </argument> + </arguments> + </type> <type name="Magento\Framework\GraphQl\Schema\Type\Entity\DefaultMapper"> <arguments> <argument name="map" xsi:type="array"> diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index b509865f0e82b..69c13a9e81923 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -403,6 +403,10 @@ interface LayerFilterItemInterface @typeResolver(class: "Magento\\CatalogGraphQl items_count: Int @doc(description: "Count of items by filter.") @deprecated(reason: "Use AggregationOption.count instead.") } +type LayerFilterItem implements LayerFilterItemInterface { + +} + type Aggregation @doc(description: "A bucket that contains information for each filterable option (such as price, category ID, and custom attributes).") { count: Int @doc(description: "The number of options in the aggregation group.") label: String @doc(description: "The aggregation display name.") @@ -410,13 +414,13 @@ type Aggregation @doc(description: "A bucket that contains information for each options: [AggregationOption] @doc(description: "Array of options for the aggregation.") } -type AggregationOption { +interface AggregationOptionInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\AggregationOptionTypeResolverComposite") { count: Int @doc(description: "The number of items that match the aggregation option.") label: String @doc(description: "Aggregation option display label.") value: String! @doc(description: "The internal ID that represents the value of the option.") } -type LayerFilterItem implements LayerFilterItemInterface { +type AggregationOption implements AggregationOptionInterface { } diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php index 37d2dda886259..f164da99292ad 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php @@ -201,8 +201,9 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu ) ->joinLeft( ['current_store' => $table], - 'current_store.attribute_id = main_table.attribute_id AND current_store.store_id = ' - . $currentStoreId, + 'current_store.entity_id = main_table.entity_id AND ' + . 'current_store.attribute_id = main_table.attribute_id AND current_store.store_id = ' + . $currentStoreId, null ) ->columns([$filter->getField() => $ifNullCondition]) diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/TemporaryStorage.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/TemporaryStorage.php index 60ee2d5706067..7f8ef8c422b92 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/TemporaryStorage.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/TemporaryStorage.php @@ -152,7 +152,7 @@ private function createTemporaryTable() self::FIELD_SCORE, Table::TYPE_DECIMAL, [32, 16], - ['unsigned' => true, 'nullable' => false], + ['unsigned' => true, 'nullable' => true], 'Score' ); $table->setOption('type', 'memory'); From 0768d89e8f2a81fb18539edaa889de94e6003818 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Mon, 9 Sep 2019 10:10:30 -0500 Subject: [PATCH 705/841] magento/graphql-ce#762: Subscribe for newsletter even when not allowed --- .../Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php index 18e417bb5edfe..6d33dea35835f 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -14,6 +14,7 @@ use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Newsletter\Model\Config; +use Magento\Store\Model\ScopeInterface; /** * Create customer account resolver @@ -66,7 +67,7 @@ public function resolve( throw new GraphQlInputException(__('"input" value should be specified')); } - if (!$this->newsLetterConfig->isActive()) { + if (!$this->newsLetterConfig->isActive(ScopeInterface::SCOPE_STORE)) { $args['input']['is_subscribed'] = false; } From cbe25f4960b59feed344eebee25e3dbe2f3efaaa Mon Sep 17 00:00:00 2001 From: Pieter Hoste <hoste.pieter@gmail.com> Date: Sat, 7 Sep 2019 14:51:53 +0200 Subject: [PATCH 706/841] Cleaned up non-existing files from being excluded from bundling. --- app/design/adminhtml/Magento/backend/etc/view.xml | 9 --------- app/design/frontend/Magento/blank/etc/view.xml | 2 -- app/design/frontend/Magento/luma/etc/view.xml | 2 -- 3 files changed, 13 deletions(-) diff --git a/app/design/adminhtml/Magento/backend/etc/view.xml b/app/design/adminhtml/Magento/backend/etc/view.xml index 18c2d8f1b1722..6539e3330e98b 100644 --- a/app/design/adminhtml/Magento/backend/etc/view.xml +++ b/app/design/adminhtml/Magento/backend/etc/view.xml @@ -24,7 +24,6 @@ </media> <exclude> <item type="file">Lib::mage/captcha.js</item> - <item type="file">Lib::mage/captcha.min.js</item> <item type="file">Lib::mage/common.js</item> <item type="file">Lib::mage/cookies.js</item> <item type="file">Lib::mage/dataPost.js</item> @@ -46,7 +45,6 @@ <item type="file">Lib::mage/translate-inline-vde.js</item> <item type="file">Lib::mage/webapi.js</item> <item type="file">Lib::mage/zoom.js</item> - <item type="file">Lib::mage/validation/dob-rule.js</item> <item type="file">Lib::mage/validation/validation.js</item> <item type="file">Lib::mage/adminhtml/varienLoader.js</item> <item type="file">Lib::mage/adminhtml/tools.js</item> @@ -57,11 +55,9 @@ <item type="file">Lib::jquery/jquery.parsequery.js</item> <item type="file">Lib::jquery/jquery.mobile.custom.js</item> <item type="file">Lib::jquery/jquery-ui.js</item> - <item type="file">Lib::jquery/jquery-ui.min.js</item> <item type="file">Lib::matchMedia.js</item> <item type="file">Lib::requirejs/require.js</item> <item type="file">Lib::requirejs/text.js</item> - <item type="file">Lib::date-format-normalizer.js</item> <item type="file">Lib::varien/js.js</item> <item type="directory">Lib::css</item> <item type="directory">Lib::lib</item> @@ -72,10 +68,5 @@ <item type="directory">Lib::fotorama</item> <item type="directory">Lib::magnifier</item> <item type="directory">Lib::tiny_mce</item> - <item type="directory">Lib::tiny_mce/classes</item> - <item type="directory">Lib::tiny_mce/langs</item> - <item type="directory">Lib::tiny_mce/plugins</item> - <item type="directory">Lib::tiny_mce/themes</item> - <item type="directory">Lib::tiny_mce/utils</item> </exclude> </view> diff --git a/app/design/frontend/Magento/blank/etc/view.xml b/app/design/frontend/Magento/blank/etc/view.xml index e742ce0a21cd1..dfc5ad39af557 100644 --- a/app/design/frontend/Magento/blank/etc/view.xml +++ b/app/design/frontend/Magento/blank/etc/view.xml @@ -262,12 +262,10 @@ <item type="file">Lib::jquery/jquery.min.js</item> <item type="file">Lib::jquery/jquery-ui-1.9.2.js</item> <item type="file">Lib::jquery/jquery.details.js</item> - <item type="file">Lib::jquery/jquery.details.min.js</item> <item type="file">Lib::jquery/jquery.hoverIntent.js</item> <item type="file">Lib::jquery/colorpicker/js/colorpicker.js</item> <item type="file">Lib::requirejs/require.js</item> <item type="file">Lib::requirejs/text.js</item> - <item type="file">Lib::date-format-normalizer.js</item> <item type="file">Lib::legacy-build.min.js</item> <item type="file">Lib::mage/captcha.js</item> <item type="file">Lib::mage/dropdown_old.js</item> diff --git a/app/design/frontend/Magento/luma/etc/view.xml b/app/design/frontend/Magento/luma/etc/view.xml index 7aa2e51481bd9..c04c0d73cb3cd 100644 --- a/app/design/frontend/Magento/luma/etc/view.xml +++ b/app/design/frontend/Magento/luma/etc/view.xml @@ -273,12 +273,10 @@ <item type="file">Lib::jquery/jquery.min.js</item> <item type="file">Lib::jquery/jquery-ui-1.9.2.js</item> <item type="file">Lib::jquery/jquery.details.js</item> - <item type="file">Lib::jquery/jquery.details.min.js</item> <item type="file">Lib::jquery/jquery.hoverIntent.js</item> <item type="file">Lib::jquery/colorpicker/js/colorpicker.js</item> <item type="file">Lib::requirejs/require.js</item> <item type="file">Lib::requirejs/text.js</item> - <item type="file">Lib::date-format-normalizer.js</item> <item type="file">Lib::legacy-build.min.js</item> <item type="file">Lib::mage/captcha.js</item> <item type="file">Lib::mage/dropdown_old.js</item> From 09ef8d2a905c52dc057b8e6776b1a2e9dc529e65 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Mon, 9 Sep 2019 12:59:46 -0500 Subject: [PATCH 707/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - fix tests affected by making url_key searchable - fix unit and integration tests --- .../Adapter/Mysql/Filter/Preprocessor.php | 4 +- .../Product/FieldProvider/StaticFieldTest.php | 94 ++++++++++++------- .../Indexer/Fulltext/Action/FullTest.php | 18 +++- .../CatalogSearch/_files/indexer_fulltext.php | 5 + .../Controller/GraphQlControllerTest.php | 2 +- .../Adapter/Mysql/TemporaryStorageTest.php | 6 +- 6 files changed, 83 insertions(+), 46 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php index f164da99292ad..a97d362c5de7f 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php @@ -201,8 +201,8 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu ) ->joinLeft( ['current_store' => $table], - 'current_store.entity_id = main_table.entity_id AND ' - . 'current_store.attribute_id = main_table.attribute_id AND current_store.store_id = ' + "current_store.{$linkIdField} = main_table.{$linkIdField} AND " + . "current_store.attribute_id = main_table.attribute_id AND current_store.store_id = " . $currentStoreId, null ) diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php index de85b8b6602b8..f90c13c9bfb65 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php @@ -139,6 +139,7 @@ public function testGetAllAttributesTypes( $isComplexType, $complexType, $isSortable, + $isTextType, $fieldName, $compositeFieldName, $sortFieldName, @@ -153,29 +154,33 @@ public function testGetAllAttributesTypes( $this->indexTypeConverter->expects($this->any()) ->method('convert') ->with($this->anything()) - ->will($this->returnCallback( - function ($type) { - if ($type === 'no_index') { - return 'no'; - } elseif ($type === 'no_analyze') { - return 'not_analyzed'; + ->will( + $this->returnCallback( + function ($type) { + if ($type === 'no_index') { + return 'no'; + } elseif ($type === 'no_analyze') { + return 'not_analyzed'; + } } - } - )); + ) + ); $this->fieldNameResolver->expects($this->any()) ->method('getFieldName') ->with($this->anything()) - ->will($this->returnCallback( - function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortFieldName) { - if (empty($context)) { - return $fieldName; - } elseif ($context['type'] === 'sort') { - return $sortFieldName; - } elseif ($context['type'] === 'text') { - return $compositeFieldName; + ->will( + $this->returnCallback( + function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortFieldName) { + if (empty($context)) { + return $fieldName; + } elseif ($context['type'] === 'sort') { + return $sortFieldName; + } elseif ($context['type'] === 'text') { + return $compositeFieldName; + } } - } - )); + ) + ); $productAttributeMock = $this->getMockBuilder(AbstractAttribute::class) ->setMethods(['getAttributeCode']) @@ -189,7 +194,7 @@ function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortF $attributeMock = $this->getMockBuilder(AttributeAdapter::class) ->disableOriginalConstructor() - ->setMethods(['isComplexType', 'getAttributeCode', 'isSortable']) + ->setMethods(['isComplexType', 'getAttributeCode', 'isSortable', 'isTextType']) ->getMock(); $attributeMock->expects($this->any()) ->method('isComplexType') @@ -197,6 +202,9 @@ function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortF $attributeMock->expects($this->any()) ->method('isSortable') ->willReturn($isSortable); + $attributeMock->expects($this->any()) + ->method('isTextType') + ->willReturn($isTextType); $attributeMock->expects($this->any()) ->method('getAttributeCode') ->willReturn($attributeCode); @@ -207,22 +215,24 @@ function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortF $this->fieldTypeConverter->expects($this->any()) ->method('convert') ->with($this->anything()) - ->will($this->returnCallback( - function ($type) use ($complexType) { - static $callCount = []; - $callCount[$type] = !isset($callCount[$type]) ? 1 : ++$callCount[$type]; + ->will( + $this->returnCallback( + function ($type) use ($complexType) { + static $callCount = []; + $callCount[$type] = !isset($callCount[$type]) ? 1 : ++$callCount[$type]; - if ($type === 'string') { - return 'string'; - } elseif ($type === 'float') { - return 'float'; - } elseif ($type === 'keyword') { - return 'string'; - } else { - return $complexType; + if ($type === 'string') { + return 'string'; + } elseif ($type === 'float') { + return 'float'; + } elseif ($type === 'keyword') { + return 'string'; + } else { + return $complexType; + } } - } - )); + ) + ); $this->assertEquals( $expected, @@ -243,13 +253,19 @@ public function attributeProvider() true, 'text', false, + true, 'category_ids', 'category_ids_value', '', [ 'category_ids' => [ 'type' => 'select', - 'index' => true + 'index' => true, + 'fields' => [ + 'keyword' => [ + 'type' => 'string', + ] + ] ], 'category_ids_value' => [ 'type' => 'string' @@ -267,13 +283,19 @@ public function attributeProvider() false, null, false, + true, 'attr_code', '', '', [ 'attr_code' => [ 'type' => 'text', - 'index' => 'no' + 'index' => 'no', + 'fields' => [ + 'keyword' => [ + 'type' => 'string', + ] + ] ], 'store_id' => [ 'type' => 'string', @@ -288,6 +310,7 @@ public function attributeProvider() false, null, false, + false, 'attr_code', '', '', @@ -308,6 +331,7 @@ public function attributeProvider() false, null, true, + false, 'attr_code', '', 'sort_attr_code', diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php index 137a3845b1efa..916af235edbd8 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php @@ -82,37 +82,45 @@ private function getExpectedIndexData() $taxClassId = $attributeRepository ->get(\Magento\Customer\Api\Data\GroupInterface::TAX_CLASS_ID) ->getAttributeId(); + $urlKeyId = $attributeRepository + ->get(\Magento\Catalog\Api\Data\ProductAttributeInterface::CODE_SEO_FIELD_URL_KEY) + ->getAttributeId(); return [ 'configurable' => [ $skuId => 'configurable', $configurableId => 'Option 2', $nameId => 'Configurable Product | Configurable OptionOption 2', $taxClassId => 'Taxable Goods | Taxable Goods', - $statusId => 'Enabled | Enabled' + $statusId => 'Enabled | Enabled', + $urlKeyId => 'configurable-product | configurable-optionoption-2' ], 'index_enabled' => [ $skuId => 'index_enabled', $nameId => 'index enabled', $taxClassId => 'Taxable Goods', - $statusId => 'Enabled' + $statusId => 'Enabled', + $urlKeyId => 'index-enabled' ], 'index_visible_search' => [ $skuId => 'index_visible_search', $nameId => 'index visible search', $taxClassId => 'Taxable Goods', - $statusId => 'Enabled' + $statusId => 'Enabled', + $urlKeyId => 'index-visible-search' ], 'index_visible_category' => [ $skuId => 'index_visible_category', $nameId => 'index visible category', $taxClassId => 'Taxable Goods', - $statusId => 'Enabled' + $statusId => 'Enabled', + $urlKeyId => 'index-visible-category' ], 'index_visible_both' => [ $skuId => 'index_visible_both', $nameId => 'index visible both', $taxClassId => 'Taxable Goods', - $statusId => 'Enabled' + $statusId => 'Enabled', + $urlKeyId => 'index-visible-both' ] ]; } diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/indexer_fulltext.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/indexer_fulltext.php index 2ed7c1a45360d..0e5987f8326a5 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/indexer_fulltext.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/_files/indexer_fulltext.php @@ -14,6 +14,7 @@ ->setWebsiteIds([1]) ->setName('Simple Product Apple') ->setSku('fulltext-1') + ->setUrlKey('fulltext-1') ->setPrice(10) ->setMetaTitle('first meta title') ->setMetaKeyword('first meta keyword') @@ -30,6 +31,7 @@ ->setWebsiteIds([1]) ->setName('Simple Product Banana') ->setSku('fulltext-2') + ->setUrlKey('fulltext-2') ->setPrice(20) ->setMetaTitle('second meta title') ->setMetaKeyword('second meta keyword') @@ -46,6 +48,7 @@ ->setWebsiteIds([1]) ->setName('Simple Product Orange') ->setSku('fulltext-3') + ->setUrlKey('fulltext-3') ->setPrice(20) ->setMetaTitle('third meta title') ->setMetaKeyword('third meta keyword') @@ -62,6 +65,7 @@ ->setWebsiteIds([1]) ->setName('Simple Product Papaya') ->setSku('fulltext-4') + ->setUrlKey('fulltext-4') ->setPrice(20) ->setMetaTitle('fourth meta title') ->setMetaKeyword('fourth meta keyword') @@ -78,6 +82,7 @@ ->setWebsiteIds([1]) ->setName('Simple Product Cherry') ->setSku('fulltext-5') + ->setUrlKey('fulltext-5') ->setPrice(20) ->setMetaTitle('fifth meta title') ->setMetaKeyword('fifth meta keyword') diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php index d0d746812ec44..b47cea971811e 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php @@ -167,7 +167,7 @@ public function testDispatchGetWithParameterizedVariables() : void /** @var ProductInterface $product */ $product = $productRepository->get('simple1'); $query = <<<QUERY -query GetProducts(\$filterInput:ProductFilterInput){ +query GetProducts(\$filterInput:ProductAttributeFilterInput){ products( filter:\$filterInput ){ diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/TemporaryStorageTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/TemporaryStorageTest.php index d71bd447cc578..cd7c0debc76c7 100644 --- a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/TemporaryStorageTest.php +++ b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/TemporaryStorageTest.php @@ -176,7 +176,7 @@ private function createTemporaryTable($persistentConnection = true) if ($persistentConnection) { $this->adapter->expects($this->once()) ->method('dropTemporaryTable'); - $tableInteractionCount += 1; + $tableInteractionCount++; } $table->expects($this->at($tableInteractionCount)) ->method('addColumn') @@ -187,14 +187,14 @@ private function createTemporaryTable($persistentConnection = true) ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Entity ID' ); - $tableInteractionCount += 1; + $tableInteractionCount++; $table->expects($this->at($tableInteractionCount)) ->method('addColumn') ->with( 'score', Table::TYPE_DECIMAL, [32, 16], - ['unsigned' => true, 'nullable' => false], + ['unsigned' => true, 'nullable' => true], 'Score' ); $table->expects($this->once()) From 5830d5d9cd06e2c8de361b5b11e2a2c6dee7a88f Mon Sep 17 00:00:00 2001 From: William Johnston <william.johnston@drip.com> Date: Mon, 9 Sep 2019 13:24:57 -0500 Subject: [PATCH 708/841] Refactor subscriber isSubscribed method to return the output of the boolean expression --- app/code/Magento/Newsletter/Model/Subscriber.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 85d512afaf262..8aeb8684343ab 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -353,11 +353,7 @@ public function isStatusChanged() */ public function isSubscribed() { - if ($this->getId() && $this->getStatus() == self::STATUS_SUBSCRIBED) { - return true; - } - - return false; + return $this->getId() && $this->getStatus() == self::STATUS_SUBSCRIBED; } /** From e3ecfb8e29ed74421075c3099d95ac286a091e02 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Mon, 9 Sep 2019 13:30:34 -0500 Subject: [PATCH 709/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed codestyle --- .../Magento/TestFramework/Dependency/PhpRule.php | 6 +++--- .../testsuite/Magento/Test/Integrity/DependencyTest.php | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php index 33f213454aaa4..913cc9448b978 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/PhpRule.php @@ -290,7 +290,7 @@ private function isPluginDependency($dependent, $dependency) protected function _caseGetUrl(string $currentModule, string &$contents): array { $pattern = '#(\->|:)(?<source>getUrl\(([\'"])(?<route_id>[a-z0-9\-_]{3,}|\*)' - .'(\/(?<controller_name>[a-z0-9\-_]+|\*))?(\/(?<action_name>[a-z0-9\-_]+|\*))?\3)#i'; + .'(/(?<controller_name>[a-z0-9\-_]+|\*))?(/(?<action_name>[a-z0-9\-_]+|\*))?\3)#i'; $dependencies = []; if (!preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER)) { @@ -304,11 +304,11 @@ protected function _caseGetUrl(string $currentModule, string &$contents): array $actionName = $item['action_name'] ?? UrlInterface::DEFAULT_ACTION_NAME; // skip rest - if ($routeId === "rest") { //MC-17627 + if ($routeId === "rest") { //MC-19890 continue; } // skip wildcards - if ($routeId === "*" || $controllerName === "*" || $actionName === "*") { //MC-17627 + if ($routeId === "*" || $controllerName === "*" || $actionName === "*") { //MC-19890 continue; } $modules = $this->routeMapper->getDependencyByRoutePath( diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index 144386df55e3a..a644f8894d08f 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -265,14 +265,12 @@ private static function getRoutesWhitelist(): array { if (is_null(self::$routesWhitelist)) { $routesWhitelistFilePattern = realpath(__DIR__) . '/_files/dependency_test/whitelist/routes_*.php'; - self::$routesWhitelist = []; + $routesWhitelist = []; foreach (glob($routesWhitelistFilePattern) as $fileName) { //phpcs:ignore Magento2.Performance.ForeachArrayMerge - self::$routesWhitelist = array_merge( - self::$routesWhitelist, - include $fileName - ); + $routesWhitelist = array_merge($routesWhitelist, include $fileName); } + self::$routesWhitelist = $routesWhitelist; } return self::$routesWhitelist; } From 7c3106a5c8f47d64fb5d18cbc7365e1fc93cf29b Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Mon, 9 Sep 2019 13:50:16 -0500 Subject: [PATCH 710/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Sort by position if only filtering --- .../Product/SearchCriteriaBuilder.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php index 810e9172d0973..0e92bbbab4259 100644 --- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php +++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/SearchCriteriaBuilder.php @@ -7,6 +7,7 @@ namespace Magento\CatalogGraphQl\DataProvider\Product; +use Magento\Catalog\Api\Data\EavAttributeInterface; use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\Search\FilterGroupBuilder; use Magento\Framework\Api\Search\SearchCriteriaInterface; @@ -84,6 +85,7 @@ public function __construct( public function build(array $args, bool $includeAggregation): SearchCriteriaInterface { $searchCriteria = $this->builder->build('products', $args); + $isSearch = !empty($args['search']); $this->updateRangeFilters($searchCriteria); if ($includeAggregation) { @@ -94,13 +96,15 @@ public function build(array $args, bool $includeAggregation): SearchCriteriaInte } $searchCriteria->setRequestName($requestName); - if (!empty($args['search'])) { + if ($isSearch) { $this->addFilter($searchCriteria, 'search_term', $args['search']); } + if (!$searchCriteria->getSortOrders()) { - $this->addDefaultSortOrder($searchCriteria); + $this->addDefaultSortOrder($searchCriteria, $isSearch); } - $this->addVisibilityFilter($searchCriteria, !empty($args['search']), !empty($args['filter'])); + + $this->addVisibilityFilter($searchCriteria, $isSearch, !empty($args['filter'])); $searchCriteria->setCurrentPage($args['currentPage']); $searchCriteria->setPageSize($args['pageSize']); @@ -168,12 +172,15 @@ private function addFilter(SearchCriteriaInterface $searchCriteria, string $fiel * Sort by relevance DESC by default * * @param SearchCriteriaInterface $searchCriteria + * @param bool $isSearch */ - private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria): void + private function addDefaultSortOrder(SearchCriteriaInterface $searchCriteria, $isSearch = false): void { + $sortField = $isSearch ? 'relevance' : EavAttributeInterface::POSITION; + $sortDirection = $isSearch ? SortOrder::SORT_DESC : SortOrder::SORT_ASC; $defaultSortOrder = $this->sortOrderBuilder - ->setField('relevance') - ->setDirection(SortOrder::SORT_DESC) + ->setField($sortField) + ->setDirection($sortDirection) ->create(); $searchCriteria->setSortOrders([$defaultSortOrder]); From 5962be16b90d0dc32439898293d666e934709e75 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Mon, 9 Sep 2019 14:13:17 -0500 Subject: [PATCH 711/841] magento/graphql-ce#777: Upgrade graphql-php library to the latest version --- composer.json | 2 +- composer.lock | 34 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/composer.json b/composer.json index 6b261b92ce309..4a179f480c9b0 100644 --- a/composer.json +++ b/composer.json @@ -52,7 +52,7 @@ "symfony/process": "~4.1.0|~4.2.0|~4.3.0", "tedivm/jshrink": "~1.3.0", "tubalmartin/cssmin": "4.1.1", - "webonyx/graphql-php": "^0.13.6", + "webonyx/graphql-php": "^0.13.8", "zendframework/zend-captcha": "^2.7.1", "zendframework/zend-code": "~3.3.0", "zendframework/zend-config": "^2.6.0", diff --git a/composer.lock b/composer.lock index 840ffd704c5d2..cb4f029182219 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "485287ce3a33ce9873eb328c8b42f3f1", + "content-hash": "fe4a8dce06cfede9180e774e43149550", "packages": [ { "name": "braintree/braintree_php", @@ -1552,28 +1552,28 @@ "authors": [ { "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" + "role": "Lead Developer", + "email": "terrafrost@php.net" }, { "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" + "role": "Developer", + "email": "pm@datasphere.ch" }, { "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" + "role": "Developer", + "email": "bantu@phpbb.com" }, { "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" + "role": "Developer", + "email": "petrich@tronic-media.com" }, { "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" + "role": "Developer", + "email": "graham@alt-three.com" } ], "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", @@ -2402,7 +2402,7 @@ }, { "name": "Gert de Pagter", - "email": "backendtea@gmail.com" + "email": "BackEndTea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -2670,16 +2670,16 @@ }, { "name": "webonyx/graphql-php", - "version": "v0.13.6", + "version": "v0.13.8", "source": { "type": "git", "url": "https://github.com/webonyx/graphql-php.git", - "reference": "123af49e46d26b0cd2e7a71a387253aa01ea9a6b" + "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/123af49e46d26b0cd2e7a71a387253aa01ea9a6b", - "reference": "123af49e46d26b0cd2e7a71a387253aa01ea9a6b", + "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/6829ae58f4c59121df1f86915fb9917a2ec595e8", + "reference": "6829ae58f4c59121df1f86915fb9917a2ec595e8", "shasum": "" }, "require": { @@ -2718,7 +2718,7 @@ "api", "graphql" ], - "time": "2019-08-07T08:16:55+00:00" + "time": "2019-08-25T10:32:47+00:00" }, { "name": "wikimedia/less.php", From a2f41c7455588d631f20e1cb7ffec879312abfd1 Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk <odubovyk@magento.com> Date: Mon, 9 Sep 2019 14:44:12 -0500 Subject: [PATCH 712/841] MC-17948: Newsletter template preview show small section with scroll when image added --- .../adminhtml/Magento/backend/web/css/styles-old.less | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/design/adminhtml/Magento/backend/web/css/styles-old.less b/app/design/adminhtml/Magento/backend/web/css/styles-old.less index 53af8933343f1..44fca79c31be5 100644 --- a/app/design/adminhtml/Magento/backend/web/css/styles-old.less +++ b/app/design/adminhtml/Magento/backend/web/css/styles-old.less @@ -4060,6 +4060,16 @@ } } +.newsletter-template-preview { + height: 100%; + .cms-revision-preview { + height: 100%; + .preview_iframe { + height: calc(~'100% - 50px'); + } + } +} + .admin__scope-old { .buttons-set { margin: 0 0 15px; From 9c413b9c746cb99044d3a57dd65ebb81255a67dd Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Mon, 9 Sep 2019 14:46:32 -0500 Subject: [PATCH 713/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL --- .../CatalogGraphQl/Model/Resolver/Products/Query/Search.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php index a6d7f8641bd86..ef83cc6132ecc 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/Query/Search.php @@ -110,6 +110,8 @@ public function getResult( } else { $maxPages = 0; } + $searchCriteria->setPageSize($realPageSize); + $searchCriteria->setCurrentPage($realCurrentPage); $productArray = []; /** @var \Magento\Catalog\Model\Product $product */ From 650a4a1b70d9ca531b8a3093eeeb2cfabe0a0e0c Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 9 Sep 2019 14:56:50 -0500 Subject: [PATCH 714/841] MC-19791: Poor performance on sales order update - string to integer --- .../Framework/Model/ResourceModel/Db/AbstractDb.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 3fd44db548149..62d2edcc89d19 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -793,10 +793,15 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) */ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) { - //quoting numeric values as strings may decrease query performance on some environments - $condition = is_numeric($object->getId()) - ? $this->getIdFieldName() . '=' . (int) $object->getId() - : $this->getConnection()->quoteInto($this->getIdFieldName() . '=?', $object->getId()); + $tableDescription = $this->getConnection() + ->describeTable($this->getMainTable()); + $preparedValue = $this->getConnection() + ->prepareColumnValue( + $tableDescription[$this->getIdFieldName()], + $object->getId() + ); + $condition = $this->getIdFieldName() . '=' . $preparedValue; + /** * Not auto increment primary key support */ From c9ed703b2d2a650c18d4e906e02c1b7be6013ee8 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Mon, 9 Sep 2019 14:57:11 -0500 Subject: [PATCH 715/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - Update performance tests to use url keys --- setup/performance-toolkit/benchmark.jmx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 8ffec6aff0422..a4128576e8e15 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -39784,7 +39784,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($name: String, $onServer: Boolean!) {\n productDetail: products(filter: { name: { eq: $name } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($url_key: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $url_key } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40032,7 +40032,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41674,7 +41674,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -42152,7 +42152,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -42716,7 +42716,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43664,7 +43664,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43762,7 +43762,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($name: String, $onServer: Boolean!) {\n productDetail: products(filter: { name: { eq: $name } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($url_key: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $url_key } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -44066,7 +44066,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($name: String, $onServer: Boolean!) {\n products(filter: { name: { eq: $name } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"name":"${product_name}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> From 92adff00b6062eae519b59b761416319114f4618 Mon Sep 17 00:00:00 2001 From: Raoul Rego <rrego@adobe.com> Date: Mon, 9 Sep 2019 15:17:27 -0500 Subject: [PATCH 716/841] MC-17627: Dependency static test does not analyze content of phtml files - Fixed codestyle --- .../static/testsuite/Magento/Test/Integrity/DependencyTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index a644f8894d08f..fa0d365061858 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -235,7 +235,7 @@ protected static function _initRules() $dbRuleTables = []; foreach (glob($replaceFilePattern) as $fileName) { //phpcs:ignore Magento2.Performance.ForeachArrayMerge - $dbRuleTables = array_merge($dbRuleTables, @include $fileName); + $dbRuleTables = array_merge($dbRuleTables, include $fileName); } self::$_rulesInstances = [ new PhpRule( From 29f99e690035f43d49062b521058658288d42d82 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 9 Sep 2019 15:41:52 -0500 Subject: [PATCH 717/841] MC-19791: Poor performance on sales order update - string to integer --- .../Model/ResourceModel/Db/AbstractDb.php | 26 +++++++++---------- .../Unit/ResourceModel/Db/AbstractDbTest.php | 9 +++++++ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 62d2edcc89d19..5db1d999a0cfc 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -11,6 +11,7 @@ use Magento\Framework\Model\ResourceModel\AbstractResource; use Magento\Framework\DB\Adapter\DuplicateException; use Magento\Framework\Phrase; +use Magento\Framework\DB\Adapter\AdapterInterface; /** * Abstract resource model @@ -301,7 +302,7 @@ public function getTable($tableName) * Get connection by resource name * * @param string $resourceName - * @return \Magento\Framework\DB\Adapter\AdapterInterface|false + * @return AdapterInterface|false */ protected function _getConnection($resourceName) { @@ -320,7 +321,7 @@ protected function _getConnection($resourceName) /** * Get connection * - * @return \Magento\Framework\DB\Adapter\AdapterInterface|false + * @return AdapterInterface|false */ public function getConnection() { @@ -793,13 +794,10 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) */ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) { - $tableDescription = $this->getConnection() - ->describeTable($this->getMainTable()); - $preparedValue = $this->getConnection() - ->prepareColumnValue( - $tableDescription[$this->getIdFieldName()], - $object->getId() - ); + /** @var AdapterInterface $connection */ + $connection = $this->getConnection(); + $tableDescription = $connection->describeTable($this->getMainTable()); + $preparedValue = $connection->prepareColumnValue($tableDescription[$this->getIdFieldName()], $object->getId()); $condition = $this->getIdFieldName() . '=' . $preparedValue; /** @@ -808,22 +806,22 @@ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) if ($this->_isPkAutoIncrement) { $data = $this->prepareDataForUpdate($object); if (!empty($data)) { - $this->getConnection()->update($this->getMainTable(), $data, $condition); + $connection->update($this->getMainTable(), $data, $condition); } } else { - $select = $this->getConnection()->select()->from( + $select = $connection->select()->from( $this->getMainTable(), [$this->getIdFieldName()] )->where( $condition ); - if ($this->getConnection()->fetchOne($select) !== false) { + if ($connection->fetchOne($select) !== false) { $data = $this->prepareDataForUpdate($object); if (!empty($data)) { - $this->getConnection()->update($this->getMainTable(), $data, $condition); + $connection->update($this->getMainTable(), $data, $condition); } } else { - $this->getConnection()->insert( + $connection->insert( $this->getMainTable(), $this->_prepareDataForSave($object) ); diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php index 4236c4f076dcb..7fb5ec4b506e2 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php @@ -425,6 +425,7 @@ public function testPrepareDataForUpdate() $connectionMock = $this->getMockBuilder(AdapterInterface::class) ->setMethods(['save']) ->getMockForAbstractClass(); + $context = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( \Magento\Framework\Model\Context::class ); @@ -451,6 +452,7 @@ public function testPrepareDataForUpdate() $this->_resourcesMock->expects($this->any())->method('getTableName')->with($data)->will( $this->returnValue('tableName') ); + $mainTableReflection = new \ReflectionProperty( AbstractDb::class, '_mainTable' @@ -465,6 +467,13 @@ public function testPrepareDataForUpdate() $idFieldNameReflection->setValue($this->_model, 'idFieldName'); $connectionMock->expects($this->any())->method('save')->with('tableName', 'idFieldName'); $connectionMock->expects($this->any())->method('quoteInto')->will($this->returnValue('idFieldName')); + $connectionMock->expects($this->any()) + ->method('describeTable') + ->with('tableName') + ->willReturn(['idFieldName' => []]); + $connectionMock->expects($this->any()) + ->method('prepareColumnValue') + ->willReturn(0); $abstractModelMock->setIdFieldName('id'); $abstractModelMock->setData( [ From 0d49eeb8e8f85dca5b0ebfb8e884b988f8fcbd9f Mon Sep 17 00:00:00 2001 From: Viktor Tymchynskyi <vtymchynskyi@magento.com> Date: Mon, 9 Sep 2019 15:43:15 -0500 Subject: [PATCH 718/841] MC-19917: 2 Place order buttons appear on the PayPal Review page with PayPal Express only --- .../Paypal/view/frontend/templates/express/review.phtml | 4 ---- app/code/Magento/Paypal/view/frontend/web/js/order-review.js | 4 +--- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/review.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/review.phtml index 7a94ac56232bc..8e222ca7eb04d 100644 --- a/app/code/Magento/Paypal/view/frontend/templates/express/review.phtml +++ b/app/code/Magento/Paypal/view/frontend/templates/express/review.phtml @@ -136,10 +136,6 @@ value="<?= $block->escapeHtml(__('Place Order')) ?>"> <span><?= $block->escapeHtml(__('Place Order')) ?></span> </button> - <button type="button" id="review-submit" class="action checkout primary" - value="<?= $block->escapeHtml(__('Place Order')) ?>"> - <span><?= $block->escapeHtml(__('Place Order')) ?></span> - </button> </div> <span class="please-wait load indicator" id="review-please-wait" style="display: none;" data-text="<?= $block->escapeHtml(__('Submitting order information...')) ?>"> diff --git a/app/code/Magento/Paypal/view/frontend/web/js/order-review.js b/app/code/Magento/Paypal/view/frontend/web/js/order-review.js index 1deee1bd76593..e3db1010693ee 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/order-review.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/order-review.js @@ -26,7 +26,6 @@ define([ agreementSelector: 'div.checkout-agreements input', isAjax: false, updateShippingMethodSubmitSelector: '#update-shipping-method-submit', - reviewSubmitSelector: '#review-submit', shippingMethodUpdateUrl: null, updateOrderSubmitUrl: null, canEditShippingMethod: false @@ -57,8 +56,7 @@ define([ this.options.updateContainerSelector ) ).find(this.options.updateOrderSelector).on('click', $.proxy(this._updateOrderHandler, this)).end() - .find(this.options.updateShippingMethodSubmitSelector).hide().end() - .find(this.options.reviewSubmitSelector).hide(); + .find(this.options.updateShippingMethodSubmitSelector).hide().end(); this._shippingTobilling(); if ($(this.options.shippingSubmitFormSelector).length && this.options.canEditShippingMethod) { From aefa7d98fe89b92e389d5ba8a266d2d5807179ef Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 9 Sep 2019 15:57:11 -0500 Subject: [PATCH 719/841] MC-19791: Poor performance on sales order update - string to integer --- .../Model/ResourceModel/Db/AbstractDb.php | 22 ++++++++++++++++++- .../Unit/ResourceModel/Db/AbstractDbTest.php | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 5db1d999a0cfc..5d9f05c3eb8c9 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -785,6 +785,24 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) } } + /** + * Check in column value should be quoted + * + * Based on column description + * + * @param array $columnDescription + * @return bool + */ + private function isNeedToQuoteValue(array $columnDescription): bool + { + $result = true; + if (!empty($columnDescription['DATA_TYPE']) + && in_array($columnDescription['DATA_TYPE'], ['smallint', 'int'])) { + $result = false; + } + return $result; + } + /** * Update existing object * @@ -798,7 +816,9 @@ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) $connection = $this->getConnection(); $tableDescription = $connection->describeTable($this->getMainTable()); $preparedValue = $connection->prepareColumnValue($tableDescription[$this->getIdFieldName()], $object->getId()); - $condition = $this->getIdFieldName() . '=' . $preparedValue; + $condition = (!$this->isNeedToQuoteValue($tableDescription[$this->getIdFieldName()])) + ? $this->getIdFieldName() . '=' . $preparedValue + : $connection->quoteInto($this->getIdFieldName() . '=?', $preparedValue); /** * Not auto increment primary key support diff --git a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php index 7fb5ec4b506e2..2a87cd774332a 100644 --- a/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php +++ b/lib/internal/Magento/Framework/Model/Test/Unit/ResourceModel/Db/AbstractDbTest.php @@ -496,7 +496,7 @@ public function testPrepareDataForUpdate() ->with( 'tableName', $newData, - 'idFieldName=0' + 'idFieldName' ); $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() From e3bedcf838dac3a965b11c75d5f6e1688e1426be Mon Sep 17 00:00:00 2001 From: Oleksandr Dubovyk <odubovyk@magento.com> Date: Mon, 9 Sep 2019 16:12:23 -0500 Subject: [PATCH 720/841] MC-17948: Newsletter template preview show small section with scroll when image added --- .../view/adminhtml/templates/preview/iframeswitcher.phtml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml b/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml index 5175080add914..20ff63a60a263 100644 --- a/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml +++ b/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml @@ -17,6 +17,7 @@ <iframe name="preview_iframe" id="preview_iframe" + class="preview_iframe" frameborder="0" title="<?= $block->escapeHtmlAttr(__('Preview')) ?>" width="100%" From 3d7a7f92eeac03a9d3d1e294acfe675c669554e1 Mon Sep 17 00:00:00 2001 From: Lena Orobei <oorobei@magento.com> Date: Mon, 9 Sep 2019 16:20:23 -0500 Subject: [PATCH 721/841] magento/graphql-ce#220: Implement exception logging --- .../Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php index b89207fe3bdb1..09fd44c19315d 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/ErrorHandlerInterface.php @@ -15,8 +15,6 @@ * GraphQL error handler * * @see \Magento\Framework\GraphQl\Query\QueryProcessor - * - * @api */ interface ErrorHandlerInterface { From b7339908bed639a9199d463fbe4c4e9c1536a106 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 9 Sep 2019 16:41:56 -0500 Subject: [PATCH 722/841] MC-19791: Poor performance on sales order update - string to integer --- .../Magento/Framework/Model/ResourceModel/Db/AbstractDb.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 5d9f05c3eb8c9..d75b7ee9be647 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -11,7 +11,6 @@ use Magento\Framework\Model\ResourceModel\AbstractResource; use Magento\Framework\DB\Adapter\DuplicateException; use Magento\Framework\Phrase; -use Magento\Framework\DB\Adapter\AdapterInterface; /** * Abstract resource model @@ -302,7 +301,7 @@ public function getTable($tableName) * Get connection by resource name * * @param string $resourceName - * @return AdapterInterface|false + * @return \Magento\Framework\DB\Adapter\AdapterInterface|false */ protected function _getConnection($resourceName) { @@ -321,7 +320,7 @@ protected function _getConnection($resourceName) /** * Get connection * - * @return AdapterInterface|false + * @return \Magento\Framework\DB\Adapter\AdapterInterface|false */ public function getConnection() { From fa77e7cc434195a21daec65bbe4a2f02314fa694 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 9 Sep 2019 16:45:33 -0500 Subject: [PATCH 723/841] MC-19791: Poor performance on sales order update - string to integer --- .../Customer/Test/Unit/Model/ResourceModel/GroupTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php index b245702ce07f9..069ddc63d74d7 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php @@ -127,7 +127,7 @@ public function testSaveWithReservedId() ] ) ->getMockForAbstractClass(); - $dbAdapter->expects($this->any())->method('describeTable')->willReturn([]); + $dbAdapter->expects($this->any())->method('describeTable')->willReturn(['customer_group_id' => []]); $dbAdapter->expects($this->any())->method('update')->willReturnSelf(); $dbAdapter->expects($this->once())->method('lastInsertId')->willReturn($expectedId); $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) From 3d1b4aad391aedd7d5acf710090b48694068e06e Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Mon, 9 Sep 2019 16:50:30 -0500 Subject: [PATCH 724/841] MC-19791: Poor performance on sales order update - string to integer --- .../Magento/Framework/Model/ResourceModel/Db/AbstractDb.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index d75b7ee9be647..9e11e2a9b9acb 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -811,7 +811,6 @@ private function isNeedToQuoteValue(array $columnDescription): bool */ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) { - /** @var AdapterInterface $connection */ $connection = $this->getConnection(); $tableDescription = $connection->describeTable($this->getMainTable()); $preparedValue = $connection->prepareColumnValue($tableDescription[$this->getIdFieldName()], $object->getId()); From d8b24bd18c1289c3c0a3762b928d8071701dd219 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 9 Sep 2019 16:54:41 -0500 Subject: [PATCH 725/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix failing tests --- .../Magento/GraphQl/Catalog/CategoryTest.php | 6 +-- .../GraphQl/Catalog/ProductSearchTest.php | 37 +++++++++---------- .../Quote/Customer/CheckoutEndToEndTest.php | 2 +- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index df8e399ce6c61..1443419153e1d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -383,7 +383,7 @@ public function testAnchorCategory() { category(id: {$categoryId}) { name - products(sort: {sku: ASC}) { + products(sort: {name: DESC}) { total_count items { sku @@ -400,8 +400,8 @@ public function testAnchorCategory() 'total_count' => 3, 'items' => [ ['sku' => '12345'], - ['sku' => 'simple'], - ['sku' => 'simple-4'] + ['sku' => 'simple-4'], + ['sku' => 'simple'] ] ] ] diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 2e3e67c65ca46..7a5079a672e87 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -43,11 +43,6 @@ class ProductSearchTest extends GraphQlAbstract */ public function testFilterLn() { - /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(Config::class); - $attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); - /** @var \Magento\Eav\Api\Data\AttributeOptionInterface[] $options */ - $options = $attribute->getOptions(); $query = <<<QUERY { products ( @@ -128,9 +123,8 @@ public function testLayeredNavigationForConfigurableProducts() $this->assertEquals(2, $response['products']['total_count']); $this->assertNotEmpty($response['products']['aggregations']); - $this->assertNotEmpty($response['products']['filters'],'Filters is empty'); + $this->assertNotEmpty($response['products']['filters'], 'Filters is empty'); $this->assertCount(2, $response['products']['aggregations'], 'Aggregation count does not match'); - //$this->assertResponseFields($response['products']['aggregations']) // Custom attribute filter layer data $this->assertResponseFields( @@ -224,6 +218,7 @@ public function testFilterProductsByDropDownCustomAttribute() products(filter:{ $attributeCode: {eq: "{$optionValue}"} } + sort:{relevance:DESC} pageSize: 3 currentPage: 1 ) @@ -278,8 +273,9 @@ public function testFilterProductsByDropDownCustomAttribute() $indexer->load('catalogsearch_fulltext'); $indexer->reindexAll(); $response = $this->graphQlQuery($query); - $this->assertEquals(3, $response['products']['total_count']); + $this->assertEquals(3, $response['products']['total_count'], 'Number of products returned is incorrect'); $this->assertTrue(count($response['products']['filters']) > 0, 'Product filters is not empty'); + $this->assertCount(3, $response['products']['aggregations'], 'Incorrect count of aggregations'); $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); //phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall for ($itemIndex = 0; $itemIndex < count($filteredProducts); $itemIndex++) { @@ -288,7 +284,7 @@ public function testFilterProductsByDropDownCustomAttribute() $this->assertResponseFields( $productItemsInResponse[$itemIndex][0], [ 'name' => $filteredProducts[$itemIndex]->getName(), - 'sku' => $filteredProducts[$itemIndex]->getSku() + 'sku' => $filteredProducts[$itemIndex]->getSku() ] ); } @@ -627,8 +623,8 @@ public function testFilterByCategoryIdAndCustomAttribute() $this->assertEquals(2, $response['products']['total_count']); /** @var ProductRepositoryInterface $productRepository */ $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); - $product1 = $productRepository->get('simple-4'); - $product2 = $productRepository->get('simple'); + $product1 = $productRepository->get('simple'); + $product2 = $productRepository->get('simple-4'); $filteredProducts = [$product1, $product2]; $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); //phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall @@ -1069,8 +1065,7 @@ public function testFilterProductsForExactMatchingName() $response = $this->graphQlQuery($query); $this->assertEquals(2, $response['products']['total_count']); $this->assertEquals(['page_size' => 2, 'current_page' => 1], $response['products']['page_info']); - $this->assertEquals - ( + $this->assertEquals( [ ['sku' => $product1->getSku(), 'name' => $product1->getName()], ['sku' => $product2->getSku(), 'name' => $product2->getName()] @@ -1192,7 +1187,7 @@ public function testFilterProductsBySingleCategoryId() QUERY; $response = $this->graphQlQuery($query); - + $this->assertEquals(2, $response['products']['total_count'], 'Incorrect count of products returned'); /** @var CategoryLinkManagement $productLinks */ $productLinks = ObjectManager::getInstance()->get(CategoryLinkManagement::class); /** @var CategoryRepositoryInterface $categoryRepository */ @@ -1300,9 +1295,9 @@ public function testSearchAndSortByRelevance() QUERY; $response = $this->graphQlQuery($query); $this->assertEquals(4, $response['products']['total_count']); - $this->assertNotEmpty($response['products']['filters'],'Filters should have the Category layer'); + $this->assertNotEmpty($response['products']['filters'], 'Filters should have the Category layer'); $this->assertEquals('Colorful Category', $response['products']['filters'][0]['filter_items'][0]['label']); - $productsInResponse = ['ocean blue Shoes', 'Blue briefs', 'Navy Striped Shoes','Grey shorts']; + $productsInResponse = ['ocean blue Shoes','Blue briefs','Navy Striped Shoes','Grey shorts']; for ($i = 0; $i < count($response['products']['items']); $i++) { $this->assertEquals($productsInResponse[$i], $response['products']['items'][$i]['name']); } @@ -1437,11 +1432,13 @@ public function testProductBasicFullTextSearchQuery() $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); $prod1 = $productRepository->get('blue_briefs'); - + $prod2 = $productRepository->get('grey_shorts'); + $prod3 = $productRepository->get('navy-striped-shoes'); + $prod4 = $productRepository->get('ocean-blue-shoes'); $response = $this->graphQlQuery($query); - $this->assertEquals(1, $response['products']['total_count']); + $this->assertEquals(4, $response['products']['total_count']); - $filteredProducts = [$prod1]; + $filteredProducts = [$prod1, $prod2, $prod3, $prod4]; $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); foreach ($productItemsInResponse as $itemIndex => $itemArray) { $this->assertNotEmpty($itemArray); @@ -1453,7 +1450,7 @@ public function testProductBasicFullTextSearchQuery() 'price' => [ 'minimalPrice' => [ 'amount' => [ - 'value' => $filteredProducts[$itemIndex]->getSpecialPrice(), + 'value' => $filteredProducts[$itemIndex]->getPrice(), 'currency' => 'USD' ] ] diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php index 5a4cc88d69623..6a06b143d5fcf 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CheckoutEndToEndTest.php @@ -158,7 +158,7 @@ private function findProduct(): string products ( filter: { sku: { - like:"simple%" + eq:"simple1" } } pageSize: 1 From 54eea3854dd82f73ec4cd33378fa8df1d4e0e110 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Mon, 9 Sep 2019 22:37:04 -0500 Subject: [PATCH 726/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix static --- .../testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php | 4 +++- .../Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 7a5079a672e87..e7f3655984b26 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -638,7 +638,7 @@ public function testFilterByCategoryIdAndCustomAttribute() ] ); } - $this->assertNotEmpty($response['products']['filters'],'filters is empty'); + $this->assertNotEmpty($response['products']['filters'], 'filters is empty'); $this->assertNotEmpty($response['products']['aggregations'], 'Aggregations should not be empty'); $this->assertCount(3, $response['products']['aggregations']); @@ -1298,6 +1298,7 @@ public function testSearchAndSortByRelevance() $this->assertNotEmpty($response['products']['filters'], 'Filters should have the Category layer'); $this->assertEquals('Colorful Category', $response['products']['filters'][0]['filter_items'][0]['label']); $productsInResponse = ['ocean blue Shoes','Blue briefs','Navy Striped Shoes','Grey shorts']; + // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall for ($i = 0; $i < count($response['products']['items']); $i++) { $this->assertEquals($productsInResponse[$i], $response['products']['items'][$i]['name']); } @@ -1553,6 +1554,7 @@ public function testFilterWithinASpecificPriceRangeSortedByPriceDESC() //verify that by default Price and category are the only layers available $filterNames = ['Category', 'Price']; $this->assertCount(2, $response['products']['filters'], 'Filter count does not match'); + // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall for ($i = 0; $i < count($response['products']['filters']); $i++) { $this->assertEquals($filterNames[$i], $response['products']['filters'][$i]['name']); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php index ed5aa9303d875..95308a350c953 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CheckoutEndToEndTest.php @@ -95,7 +95,7 @@ private function findProduct(): string products ( filter: { sku: { - like:"simple%" + eq:"simple1" } } pageSize: 1 From b198d112e9851389a5abd17feb5dbec093bb26a3 Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Tue, 10 Sep 2019 11:21:32 +0530 Subject: [PATCH 727/841] add digits validation for cron configuration --- app/code/Magento/Cron/etc/adminhtml/system.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/code/Magento/Cron/etc/adminhtml/system.xml b/app/code/Magento/Cron/etc/adminhtml/system.xml index 95d8d4c8a6966..c8753f1b0b56f 100644 --- a/app/code/Magento/Cron/etc/adminhtml/system.xml +++ b/app/code/Magento/Cron/etc/adminhtml/system.xml @@ -15,21 +15,27 @@ <label>Cron configuration options for group: </label> <field id="schedule_generate_every" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Generate Schedules Every</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="schedule_ahead_for" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Schedule Ahead for</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="schedule_lifetime" translate="label" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Missed if Not Run Within</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="history_cleanup_every" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>History Cleanup Every</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="history_success_lifetime" translate="label" type="text" sortOrder="50" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Success History Lifetime</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="history_failure_lifetime" translate="label" type="text" sortOrder="60" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Failure History Lifetime</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> <field id="use_separate_process" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Use Separate Process</label> From 635c38f3177b59125541fd2936c7ec48df829470 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Tue, 10 Sep 2019 12:44:49 +0300 Subject: [PATCH 728/841] Fixing the confirm style --- .../view/adminhtml/templates/rule/edit.phtml | 88 ++++++++++--------- 1 file changed, 47 insertions(+), 41 deletions(-) diff --git a/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml b/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml index 81bdd874ead6c..17862fd03bfd5 100644 --- a/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml +++ b/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml @@ -10,11 +10,12 @@ require([ 'jquery', 'Magento_Ui/js/modal/alert', + 'Magento_Ui/js/modal/confirm', "jquery/ui", 'mage/multiselect', "mage/mage", 'Magento_Ui/js/modal/modal' -], function($, alert){ +], function($, alert, confirm) { $.widget("adminhtml.dialogRates", $.mage.modal, { options: { @@ -160,53 +161,58 @@ require([ taxRateField.find('.mselect-list') .on('click.mselect-edit', '.mselect-edit', this.edit) .on("click.mselect-delete", ".mselect-delete", function () { - if (!confirm('<?= $block->escapeJs(__('Do you really want to delete this tax rate?')) ?>')) { - return; - } - var that = $(this), select = that.closest('.mselect-list').prev(), rateValue = that.parent().find('input[type="checkbox"]').val(); - $('body').trigger('processStart'); - var ajaxOptions = { - type: 'POST', - data: { - tax_calculation_rate_id: rateValue, - form_key: $('input[name="form_key"]').val() - }, - dataType: 'json', - url: '<?= $block->escapeJs($block->escapeUrl($block->getTaxRateDeleteUrl())) ?>', - success: function(result, status) { - $('body').trigger('processStop'); - if (result.success) { - that.parent().remove(); - select.find('option').each(function() { - if (this.value === rateValue) { - $(this).remove(); + confirm({ + content: '<?= /* @escapeNotVerified */ __('Do you really want to delete this tax rate?') ?>', + actions: { + /** + * Confirm action. + */ + confirm: function () { + $('body').trigger('processStart'); + var ajaxOptions = { + type: 'POST', + data: { + tax_calculation_rate_id: rateValue, + form_key: $('input[name="form_key"]').val() + }, + dataType: 'json', + url: '<?= $block->escapeJs($block->escapeUrl($block->getTaxRateDeleteUrl())) ?>', + success: function(result, status) { + $('body').trigger('processStop'); + if (result.success) { + that.parent().remove(); + select.find('option').each(function() { + if (this.value === rateValue) { + $(this).remove(); + } + }); + select.trigger('change.hiddenSelect'); + } else { + if (result.error_message) + alert({ + content: result.error_message + }); + else + alert({ + content: '<?= $block->escapeJs($block->escapeHtml(__('An error occurred'))) ?>' + }); + } + }, + error: function () { + $('body').trigger('processStop'); + alert({ + content: '<?= $block->escapeJs($block->escapeHtml(__('An error occurred'))) ?>' + }); } - }); - select.trigger('change.hiddenSelect'); - } else { - if (result.error_message) - alert({ - content: result.error_message - }); - else - alert({ - content: '<?= $block->escapeJs($block->escapeHtml(__('An error occurred'))) ?>' - }); + }; + $.ajax(ajaxOptions); } - }, - error: function () { - $('body').trigger('processStop'); - alert({ - content: '<?= $block->escapeJs($block->escapeHtml(__('An error occurred'))) ?>' - }); } - }; - $.ajax(ajaxOptions); - + }); }) .on('click.mselectAdd', '.mselect-button-add', function () { taxRateForm From 054d959deb2c1bd7b5ebe5173d9604c90eb38403 Mon Sep 17 00:00:00 2001 From: eduard13 <e.chitoraga@atwix.com> Date: Tue, 10 Sep 2019 14:35:00 +0300 Subject: [PATCH 729/841] Escaping text --- app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml b/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml index 17862fd03bfd5..3558d359aa4d6 100644 --- a/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml +++ b/app/code/Magento/Tax/view/adminhtml/templates/rule/edit.phtml @@ -166,7 +166,7 @@ require([ rateValue = that.parent().find('input[type="checkbox"]').val(); confirm({ - content: '<?= /* @escapeNotVerified */ __('Do you really want to delete this tax rate?') ?>', + content: '<?= $block->escapeJs(__('Do you really want to delete this tax rate?')) ?>', actions: { /** * Confirm action. From f940ae4b013a16a028092d6541bc84915854d91c Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <51681487+engcom-Foxtrot@users.noreply.github.com> Date: Tue, 10 Sep 2019 14:37:38 +0300 Subject: [PATCH 730/841] magento/magento2#24482: Static test fix. --- app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml b/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml index 480615cba67de..4e7ed34ed4a7e 100644 --- a/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml +++ b/app/code/Magento/Sitemap/view/adminhtml/templates/js.phtml @@ -1,3 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> + <script type="text/x-magento-init"> { "#edit_form": { From 0f140b0a317ec72c23b7849753d5a8adbe983aa9 Mon Sep 17 00:00:00 2001 From: William Johnston <william.johnston@drip.com> Date: Tue, 10 Sep 2019 08:29:20 -0500 Subject: [PATCH 731/841] Tighten equality --- app/code/Magento/Newsletter/Model/Subscriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 8aeb8684343ab..311cd00095a74 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -353,7 +353,7 @@ public function isStatusChanged() */ public function isSubscribed() { - return $this->getId() && $this->getStatus() == self::STATUS_SUBSCRIBED; + return $this->getId() && $this->getStatus() === self::STATUS_SUBSCRIBED; } /** From f0b9a96f316ca0e2c4cc8b2421896aa54324dd67 Mon Sep 17 00:00:00 2001 From: William Johnston <william@johnstonhaus.us> Date: Tue, 10 Sep 2019 10:37:14 -0400 Subject: [PATCH 732/841] Update app/code/Magento/Newsletter/Model/Subscriber.php Co-Authored-By: Ihor Sviziev <ihor-sviziev@users.noreply.github.com> --- app/code/Magento/Newsletter/Model/Subscriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 311cd00095a74..c5eee5e3cf771 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -353,7 +353,7 @@ public function isStatusChanged() */ public function isSubscribed() { - return $this->getId() && $this->getStatus() === self::STATUS_SUBSCRIBED; + return $this->getId() && (int)$this->getStatus() === self::STATUS_SUBSCRIBED; } /** From 3acfc94d0d6d0dfa544c2317d26daab5978e9193 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p.bystritsky@yandex.ru> Date: Wed, 28 Aug 2019 12:52:03 +0300 Subject: [PATCH 733/841] magento/magento2#23966: WYSIWYG image upload dialog hangs up. --- .../Test/AdminAddImageToWYSIWYGProductTest.xml | 5 ++--- .../Test/AdminAddImageToWYSIWYGBlockTest.xml | 3 --- .../Test/AdminAddImageToWYSIWYGCMSTest.xml | 3 --- .../AdminAddImageToWYSIWYGNewsletterTest.xml | 3 --- lib/web/mage/adminhtml/browser.js | 8 ++++++-- .../wysiwyg/tiny_mce/tinymce4Adapter.js | 18 +++++++++++------- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml index f3d3e653b260b..ee105320c5f29 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml @@ -16,9 +16,6 @@ <description value="Admin should be able to add image to WYSIWYG Editor on Product Page"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84375"/> - <skip> - <issueId value="MC-17232"/> - </skip> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="login"/> @@ -78,6 +75,8 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoading13"/> <see selector="{{ProductShortDescriptionWYSIWYGToolbarSection.CreateFolder}}" userInput="Create Folder" stepKey="seeCreateFolderBtn2" /> <waitForLoadingMaskToDisappear stepKey="waitForLoading14"/> + <click userInput="Storage Root" stepKey="clickOnRootFolder" /> + <waitForLoadingMaskToDisappear stepKey="waitForLoading15"/> <dontSeeElement selector="{{ProductShortDescriptionWYSIWYGToolbarSection.InsertFile}}" stepKey="dontSeeAddSelectedBtn3" /> <attachFile selector="{{ProductShortDescriptionWYSIWYGToolbarSection.BrowseUploadImage}}" userInput="{{ImageUpload3.value}}" stepKey="uploadImage3"/> <waitForLoadingMaskToDisappear stepKey="waitForFileUpload3"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml index 5baf75d43c53f..03edc69e6d625 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml @@ -16,9 +16,6 @@ <description value="Admin should be able to add image to WYSIWYG content of Block"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84376"/> - <skip> - <issueId value="MC-17232"/> - </skip> </annotations> <before> <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml index e63a6be51bcc0..205850f888797 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGCMSTest.xml @@ -16,9 +16,6 @@ <description value="Admin should be able to add image to WYSIWYG content of CMS Page"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-85825"/> - <skip> - <issueId value="MC-17232"/> - </skip> </annotations> <before> <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml index 510f3e16e8d8e..0371c0265d149 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/AdminAddImageToWYSIWYGNewsletterTest.xml @@ -16,9 +16,6 @@ <description value="Admin should be able to add image to WYSIWYG content Newsletter"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84377"/> - <skip> - <issueId value="MC-17233"/> - </skip> </annotations> <before> <actionGroup ref="LoginActionGroup" stepKey="login"/> diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js index 9019e941bc586..20bcdd36baca5 100644 --- a/lib/web/mage/adminhtml/browser.js +++ b/lib/web/mage/adminhtml/browser.js @@ -52,8 +52,11 @@ define([ content = '<div class="popup-window" id="' + windowId + '"></div>', self = this; - if (this.modalLoaded === true) { - if (options && typeof options.closed !== 'undefined') { + if (this.modalLoaded === true + && options + && self.targetElementId + && self.targetElementId === options.targetElementId) { + if (typeof options.closed !== 'undefined') { this.modal.modal('option', 'closed', options.closed); } this.modal.modal('openModal'); @@ -85,6 +88,7 @@ define([ }).done(function (data) { self.modal.html(data).trigger('contentUpdated'); self.modalLoaded = true; + self.targetElementId = options.targetElementId; }); }, diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 227ee10a73e64..4dafc845309cb 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -377,6 +377,7 @@ define([ var typeTitle = this.translate('Select Images'), storeId = this.config['store_id'] !== null ? this.config['store_id'] : 0, frameDialog = jQuery('div.mce-container[role="dialog"]'), + self = this, wUrl = this.config['files_browser_window_url'] + 'target_element_id/' + this.getId() + '/' + 'store/' + storeId + '/'; @@ -393,14 +394,17 @@ define([ require(['mage/adminhtml/browser'], function () { MediabrowserUtility.openDialog(wUrl, false, false, typeTitle, { - /** - * Closed. - */ - closed: function () { - frameDialog.show(); - jQuery('#mce-modal-block').show(); + /** + * Closed. + */ + closed: function () { + frameDialog.show(); + jQuery('#mce-modal-block').show(); + }, + + targetElementId: self.activeEditor() ? self.activeEditor().id : null } - }); + ); }); }, From d79b8a6c5d2ca75e3eb2779eb9c3aaf3ba802aed Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Tue, 10 Sep 2019 12:19:40 -0500 Subject: [PATCH 734/841] MC-19791: Poor performance on sales order update - string to integer --- .../Framework/Model/ResourceModel/Db/AbstractDb.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 9e11e2a9b9acb..34265bcca6310 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -792,11 +792,11 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) * @param array $columnDescription * @return bool */ - private function isNeedToQuoteValue(array $columnDescription): bool + private function isNumericValue(array $columnDescription): bool { $result = true; if (!empty($columnDescription['DATA_TYPE']) - && in_array($columnDescription['DATA_TYPE'], ['smallint', 'int'])) { + && in_array($columnDescription['DATA_TYPE'], ['tinyint', 'smallint', 'mediumint', 'int', 'bigint'])) { $result = false; } return $result; @@ -814,8 +814,8 @@ protected function updateObject(\Magento\Framework\Model\AbstractModel $object) $connection = $this->getConnection(); $tableDescription = $connection->describeTable($this->getMainTable()); $preparedValue = $connection->prepareColumnValue($tableDescription[$this->getIdFieldName()], $object->getId()); - $condition = (!$this->isNeedToQuoteValue($tableDescription[$this->getIdFieldName()])) - ? $this->getIdFieldName() . '=' . $preparedValue + $condition = (!$this->isNumericValue($tableDescription[$this->getIdFieldName()])) + ? sprintf('%s=%d', $this->getIdFieldName(), $preparedValue) : $connection->quoteInto($this->getIdFieldName() . '=?', $preparedValue); /** From 62e5233ee9f725ee30aade3449f563b9089f5c81 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Tue, 10 Sep 2019 13:51:22 -0500 Subject: [PATCH 735/841] MC-19857: Remaining amount discrepancy in Admin Panel for RMA --- .../Magento/Framework/Model/ResourceModel/Db/AbstractDb.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 34265bcca6310..90fa73de427bc 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -785,7 +785,7 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) } /** - * Check in column value should be quoted + * Check is column data type is numeric * * Based on column description * From f73e645a86691fda1ed8321d2b160854f4b753e5 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Tue, 10 Sep 2019 13:51:22 -0500 Subject: [PATCH 736/841] MC-19791: Poor performance on sales order update - string to integer --- .../Magento/Framework/Model/ResourceModel/Db/AbstractDb.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 34265bcca6310..90fa73de427bc 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -785,7 +785,7 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) } /** - * Check in column value should be quoted + * Check is column data type is numeric * * Based on column description * From 091eb7b900fd8292dec57ab312d7e79c3ba524c6 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko <ymiroshnychenko@magento.com> Date: Tue, 10 Sep 2019 14:21:07 -0500 Subject: [PATCH 737/841] MC-19421: Reduce q-ty of Reports Created in /app/*/var/report --- app/etc/di.xml | 1 + .../Magento/Framework/Error/ProcessorTest.php | 122 ++++++-- .../Rule/Design/AllPurposeAction.php | 2 +- .../Framework/App/ExceptionHandler.php | 284 ++++++++++++++++++ .../App/ExceptionHandlerInterface.php | 31 ++ lib/internal/Magento/Framework/App/Http.php | 243 ++------------- .../App/Test/Unit/ExceptionHandlerTest.php | 277 +++++++++++++++++ .../Framework/App/Test/Unit/HttpTest.php | 115 +------ .../App/Test/Unit/_files/pub/errors/404.php | 6 + .../Test/Unit/_files/pub/errors/report.php | 6 + pub/errors/local.xml.sample | 17 ++ pub/errors/processor.php | 186 ++++++++++-- 12 files changed, 910 insertions(+), 380 deletions(-) create mode 100644 lib/internal/Magento/Framework/App/ExceptionHandler.php create mode 100644 lib/internal/Magento/Framework/App/ExceptionHandlerInterface.php create mode 100644 lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php create mode 100644 lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/404.php create mode 100644 lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/report.php diff --git a/app/etc/di.xml b/app/etc/di.xml index 50088d41f1b4b..882d1be623988 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -47,6 +47,7 @@ <preference for="Magento\Framework\App\RequestSafetyInterface" type="Magento\Framework\App\Request\Http" /> <preference for="\Magento\Framework\Setup\SchemaSetupInterface" type="\Magento\Setup\Module\Setup" /> <preference for="\Magento\Framework\Setup\ModuleDataSetupInterface" type="\Magento\Setup\Module\DataSetup" /> + <preference for="Magento\Framework\App\ExceptionHandlerInterface" type="Magento\Framework\App\ExceptionHandler" /> <type name="Magento\Store\Model\Store"> <arguments> <argument name="currencyInstalled" xsi:type="string">system/currency/installed</argument> diff --git a/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php b/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php index 515e1a898dfac..51441e4879539 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php @@ -5,52 +5,136 @@ */ namespace Magento\Framework\Error; +use Magento\TestFramework\Helper\Bootstrap; + require_once __DIR__ . '/../../../../../../../pub/errors/processor.php'; class ProcessorTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\Framework\Error\Processor */ + /** + * @var Processor + */ private $processor; + /** + * @inheritdoc + */ public function setUp() { $this->processor = $this->createProcessor(); } + /** + * {@inheritdoc} + * @throws \Exception + */ public function tearDown() { - if ($this->processor->reportId) { - unlink($this->processor->_reportDir . '/' . $this->processor->reportId); - } + $reportDir = $this->processor->_reportDir; + $this->removeDirRecursively($reportDir); } - public function testSaveAndLoadReport() - { + /** + * @param int $logReportDirNestingLevel + * @param int $logReportDirNestingLevelChanged + * @param string $exceptionMessage + * @dataProvider dataProviderSaveAndLoadReport + */ + public function testSaveAndLoadReport( + int $logReportDirNestingLevel, + int $logReportDirNestingLevelChanged, + string $exceptionMessage + ) { + $_ENV['MAGE_LOG_REPORT_DIR_NESTING_LEVEL'] = $logReportDirNestingLevel; $reportData = [ - 0 => 'exceptionMessage', + 0 => $exceptionMessage, 1 => 'exceptionTrace', 'script_name' => 'processor.php' ]; + $reportData['report_id'] = hash('sha256', implode('', $reportData)); $expectedReportData = array_merge($reportData, ['url' => '']); - $this->processor = $this->createProcessor(); - $this->processor->saveReport($reportData); - if (!$this->processor->reportId) { + $processor = $this->createProcessor(); + $processor->saveReport($reportData); + $reportId = $processor->reportId; + if (!$reportId) { $this->fail("Failed to generate report id"); } - $this->assertFileExists($this->processor->_reportDir . '/' . $this->processor->reportId); - $this->assertEquals($expectedReportData, $this->processor->reportData); + $this->assertEquals($expectedReportData, $processor->reportData); + $_ENV['MAGE_LOG_REPORT_DIR_NESTING_LEVEL'] = $logReportDirNestingLevelChanged; + $processor = $this->createProcessor(); + $processor->loadReport($reportId); + $this->assertEquals($expectedReportData, $processor->reportData, "File contents of report don't match"); + } + + /** + * Data Provider for testSaveAndLoadReport + * + * @return array + */ + public function dataProviderSaveAndLoadReport(): array + { + return [ + [ + 'logReportDirNestingLevel' => 0, + 'logReportDirNestingLevelChanged' => 0, + 'exceptionMessage' => '$exceptionMessage 0', + ], + [ + 'logReportDirNestingLevel' => 1, + 'logReportDirNestingLevelChanged' => 1, + 'exceptionMessage' => '$exceptionMessage 1', + ], + [ + 'logReportDirNestingLevel' => 2, + 'logReportDirNestingLevelChanged' => 2, + 'exceptionMessage' => '$exceptionMessage 2', + ], + [ + 'logReportDirNestingLevel' => 3, + 'logReportDirNestingLevelChanged' => 23, + 'exceptionMessage' => '$exceptionMessage 2', + ], + [ + 'logReportDirNestingLevel' => 32, + 'logReportDirNestingLevelChanged' => 32, + 'exceptionMessage' => '$exceptionMessage 3', + ], + [ + 'logReportDirNestingLevel' => 100, + 'logReportDirNestingLevelChanged' => 100, + 'exceptionMessage' => '$exceptionMessage 100', + ], + ]; + } - $loadProcessor = $this->createProcessor(); - $loadProcessor->loadReport($this->processor->reportId); - $this->assertEquals($expectedReportData, $loadProcessor->reportData, "File contents of report don't match"); + /** + * @return Processor + */ + private function createProcessor(): Processor + { + return Bootstrap::getObjectManager()->create(Processor::class); } /** - * @return \Magento\Framework\Error\Processor + * Remove dir recursively + * + * @param string $dir + * @param int $i + * @return bool + * @throws \Exception */ - private function createProcessor() + private function removeDirRecursively(string $dir, int $i = 0): bool { - return \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(\Magento\Framework\Error\Processor::class); + if ($i >= 100) { + throw new \Exception('Emergency exit from recursion'); + } + $files = array_diff(scandir($dir), ['.', '..']); + foreach ($files as $file) { + $i++; + (is_dir("$dir/$file")) + ? $this->removeDirRecursively("$dir/$file", $i) + : unlink("$dir/$file"); + } + return rmdir($dir); } } diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/AllPurposeAction.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/AllPurposeAction.php index ca257c1f6eb39..00a6e49d4e31d 100644 --- a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/AllPurposeAction.php +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/AllPurposeAction.php @@ -38,7 +38,7 @@ public function apply(AbstractNode $node) return; } - if (in_array(ActionInterface::class, $impl, true)) { + if (is_array($impl) && in_array(ActionInterface::class, $impl, true)) { $methodsDefined = false; foreach ($impl as $i) { if (preg_match('/\\\Http[a-z]+ActionInterface$/i', $i)) { diff --git a/lib/internal/Magento/Framework/App/ExceptionHandler.php b/lib/internal/Magento/Framework/App/ExceptionHandler.php new file mode 100644 index 0000000000000..38e85326ef5e4 --- /dev/null +++ b/lib/internal/Magento/Framework/App/ExceptionHandler.php @@ -0,0 +1,284 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\App; + +use Magento\Framework\App\Response\Http as ResponseHttp; +use Magento\Framework\App\Request\Http as RequestHttp; +use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Debug; +use Magento\Framework\Filesystem; +use Psr\Log\LoggerInterface; +use Magento\Framework\Exception\SessionException; +use Magento\Framework\Exception\State\InitException; + +/** + * Handler of HTTP web application exception + */ +class ExceptionHandler implements ExceptionHandlerInterface +{ + /** + * @var Filesystem + */ + protected $_filesystem; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var EncryptorInterface + */ + private $encryptor; + + /** + * @param EncryptorInterface $encryptor + * @param Filesystem $filesystem + * @param LoggerInterface $logger + */ + public function __construct( + EncryptorInterface $encryptor, + Filesystem $filesystem, + LoggerInterface $logger + ) { + $this->encryptor = $encryptor; + $this->_filesystem = $filesystem; + $this->logger = $logger; + } + + /** + * Handles exception of HTTP web application + * + * @param Bootstrap $bootstrap + * @param \Exception $exception + * @param ResponseHttp $response + * @param RequestHttp $request + * @return bool + */ + public function handle( + Bootstrap $bootstrap, + \Exception $exception, + ResponseHttp $response, + RequestHttp $request + ): bool { + $result = $this->handleDeveloperMode($bootstrap, $exception, $response) + || $this->handleBootstrapErrors($bootstrap, $exception, $response) + || $this->handleSessionException($exception, $response, $request) + || $this->handleInitException($exception) + || $this->handleGenericReport($bootstrap, $exception); + return $result; + } + + /** + * Error handler for developer mode + * + * @param Bootstrap $bootstrap + * @param \Exception $exception + * @param ResponseHttp $response + * @return bool + */ + private function handleDeveloperMode( + Bootstrap $bootstrap, + \Exception $exception, + ResponseHttp $response + ): bool { + if ($bootstrap->isDeveloperMode()) { + if (Bootstrap::ERR_IS_INSTALLED == $bootstrap->getErrorCode()) { + try { + $this->redirectToSetup($bootstrap, $exception, $response); + return true; + } catch (\Exception $e) { + $exception = $e; + } + } + $response->setHttpResponseCode(500); + $response->setHeader('Content-Type', 'text/plain'); + $response->setBody($this->buildContentFromException($exception)); + $response->sendResponse(); + return true; + } + return false; + } + + /** + * Build content based on an exception + * + * @param \Exception $exception + * @return string + */ + private function buildContentFromException(\Exception $exception): string + { + /** @var \Exception[] $exceptions */ + $exceptions = []; + + do { + $exceptions[] = $exception; + } while ($exception = $exception->getPrevious()); + + $buffer = sprintf("%d exception(s):\n", count($exceptions)); + + foreach ($exceptions as $index => $exception) { + $buffer .= sprintf( + "Exception #%d (%s): %s\n", + $index, + get_class($exception), + $exception->getMessage() + ); + } + + foreach ($exceptions as $index => $exception) { + $buffer .= sprintf( + "\nException #%d (%s): %s\n%s\n", + $index, + get_class($exception), + $exception->getMessage(), + Debug::trace( + $exception->getTrace(), + true, + true, + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') + ) + ); + } + + return $buffer; + } + + /** + * Handler for bootstrap errors + * + * @param Bootstrap $bootstrap + * @param \Exception $exception + * @param ResponseHttp $response + * @return bool + */ + private function handleBootstrapErrors( + Bootstrap $bootstrap, + \Exception &$exception, + ResponseHttp $response + ): bool { + $bootstrapCode = $bootstrap->getErrorCode(); + if (Bootstrap::ERR_MAINTENANCE == $bootstrapCode) { + // phpcs:ignore Magento2.Security.IncludeFile + require $this->_filesystem + ->getDirectoryRead(DirectoryList::PUB) + ->getAbsolutePath('errors/503.php'); + return true; + } + if (Bootstrap::ERR_IS_INSTALLED == $bootstrapCode) { + try { + $this->redirectToSetup($bootstrap, $exception, $response); + return true; + } catch (\Exception $e) { + $exception = $e; + } + } + return false; + } + + /** + * Handler for session errors + * + * @param \Exception $exception + * @param ResponseHttp $response + * @param RequestHttp $request + * @return bool + */ + private function handleSessionException( + \Exception $exception, + ResponseHttp $response, + RequestHttp $request + ): bool { + if ($exception instanceof SessionException) { + $response->setRedirect($request->getDistroBaseUrl()); + $response->sendHeaders(); + return true; + } + return false; + } + + /** + * Handler for application initialization errors + * + * @param \Exception $exception + * @return bool + */ + private function handleInitException(\Exception $exception): bool + { + if ($exception instanceof InitException) { + $this->logger->critical($exception); + // phpcs:ignore Magento2.Security.IncludeFile + require $this->_filesystem + ->getDirectoryRead(DirectoryList::PUB) + ->getAbsolutePath('errors/404.php'); + return true; + } + return false; + } + + /** + * Handle for any other errors + * + * @param Bootstrap $bootstrap + * @param \Exception $exception + * @return bool + */ + private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception): bool + { + $reportData = [ + $exception->getMessage(), + Debug::trace( + $exception->getTrace(), + true, + false, + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') + ) + ]; + $params = $bootstrap->getParams(); + if (isset($params['REQUEST_URI'])) { + $reportData['url'] = $params['REQUEST_URI']; + } + if (isset($params['SCRIPT_NAME'])) { + $reportData['script_name'] = $params['SCRIPT_NAME']; + } + $reportData['report_id'] = $this->encryptor->getHash(implode('', $reportData)); + $this->logger->critical($exception, ['report_id' => $reportData['report_id']]); + // phpcs:ignore Magento2.Security.IncludeFile + require $this->_filesystem + ->getDirectoryRead(DirectoryList::PUB) + ->getAbsolutePath('errors/report.php'); + return true; + } + + /** + * If not installed, try to redirect to installation wizard + * + * @param Bootstrap $bootstrap + * @param \Exception $exception + * @param ResponseHttp $response + * @return void + * @throws \Exception + */ + private function redirectToSetup(Bootstrap $bootstrap, \Exception $exception, ResponseHttp $response) + { + $setupInfo = new SetupInfo($bootstrap->getParams()); + $projectRoot = $this->_filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath(); + if ($setupInfo->isAvailable()) { + $response->setRedirect($setupInfo->getUrl()); + $response->sendHeaders(); + } else { + $newMessage = $exception->getMessage() . "\nNOTE: You cannot install Magento using the Setup Wizard " + . "because the Magento setup directory cannot be accessed. \n" + . 'You can install Magento using either the command line or you must restore access ' + . 'to the following directory: ' . $setupInfo->getDir($projectRoot) . "\n"; + // phpcs:ignore Magento2.Exceptions.DirectThrow + throw new \Exception($newMessage, 0, $exception); + } + } +} diff --git a/lib/internal/Magento/Framework/App/ExceptionHandlerInterface.php b/lib/internal/Magento/Framework/App/ExceptionHandlerInterface.php new file mode 100644 index 0000000000000..b4bb5d555017d --- /dev/null +++ b/lib/internal/Magento/Framework/App/ExceptionHandlerInterface.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\App; + +use Magento\Framework\App\Response\Http as ResponseHttp; +use Magento\Framework\App\Request\Http as RequestHttp; + +/** + * Interface ExceptionHandler + */ +interface ExceptionHandlerInterface +{ + /** + * Handles exception of HTTP web application + * + * @param Bootstrap $bootstrap + * @param \Exception $exception + * @param ResponseHttp $response + * @param RequestHttp $request + * @return bool + */ + public function handle( + Bootstrap $bootstrap, + \Exception $exception, + ResponseHttp $response, + RequestHttp $request + ): bool; +} diff --git a/lib/internal/Magento/Framework/App/Http.php b/lib/internal/Magento/Framework/App/Http.php index ca3976da1df52..e69a858ee5a55 100644 --- a/lib/internal/Magento/Framework/App/Http.php +++ b/lib/internal/Magento/Framework/App/Http.php @@ -3,18 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Framework\App; -use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\Request\Http as RequestHttp; use Magento\Framework\App\Response\Http as ResponseHttp; use Magento\Framework\App\Response\HttpInterface; use Magento\Framework\Controller\ResultInterface; -use Magento\Framework\Debug; use Magento\Framework\Event; -use Magento\Framework\Filesystem; use Magento\Framework\ObjectManager\ConfigLoaderInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Event\Manager; +use Magento\Framework\Registry; /** * HTTP web application. Called from webroot index.php to serve web requests. @@ -24,12 +24,12 @@ class Http implements \Magento\Framework\AppInterface { /** - * @var \Magento\Framework\ObjectManagerInterface + * @var ObjectManagerInterface */ protected $_objectManager; /** - * @var \Magento\Framework\Event\Manager + * @var Manager */ protected $_eventManager; @@ -53,47 +53,42 @@ class Http implements \Magento\Framework\AppInterface */ protected $_state; - /** - * @var Filesystem - */ - protected $_filesystem; - /** * @var ResponseHttp */ protected $_response; /** - * @var \Magento\Framework\Registry + * @var Registry */ protected $registry; /** - * @var \Psr\Log\LoggerInterface + * @var ExceptionHandlerInterface */ - private $logger; + private $exceptionHandler; /** - * @param \Magento\Framework\ObjectManagerInterface $objectManager + * @param ObjectManagerInterface $objectManager * @param Event\Manager $eventManager * @param AreaList $areaList * @param RequestHttp $request * @param ResponseHttp $response * @param ConfigLoaderInterface $configLoader * @param State $state - * @param Filesystem $filesystem - * @param \Magento\Framework\Registry $registry + * @param Registry $registry + * @param ExceptionHandlerInterface $exceptionHandler */ public function __construct( - \Magento\Framework\ObjectManagerInterface $objectManager, + ObjectManagerInterface $objectManager, Event\Manager $eventManager, AreaList $areaList, RequestHttp $request, ResponseHttp $response, ConfigLoaderInterface $configLoader, State $state, - Filesystem $filesystem, - \Magento\Framework\Registry $registry + Registry $registry, + ExceptionHandlerInterface $exceptionHandler = null ) { $this->_objectManager = $objectManager; $this->_eventManager = $eventManager; @@ -102,30 +97,15 @@ public function __construct( $this->_response = $response; $this->_configLoader = $configLoader; $this->_state = $state; - $this->_filesystem = $filesystem; $this->registry = $registry; - } - - /** - * Add new dependency - * - * @return \Psr\Log\LoggerInterface - * - * @deprecated 100.1.0 - */ - private function getLogger() - { - if (!$this->logger instanceof \Psr\Log\LoggerInterface) { - $this->logger = \Magento\Framework\App\ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); - } - return $this->logger; + $this->exceptionHandler = $exceptionHandler ?: $this->_objectManager->get(ExceptionHandlerInterface::class); } /** * Run application * - * @throws \InvalidArgumentException * @return ResponseInterface + * @throws LocalizedException|\InvalidArgumentException */ public function launch() { @@ -172,193 +152,8 @@ private function handleHeadRequest() /** * @inheritdoc */ - public function catchException(Bootstrap $bootstrap, \Exception $exception) - { - $result = $this->handleDeveloperMode($bootstrap, $exception) - || $this->handleBootstrapErrors($bootstrap, $exception) - || $this->handleSessionException($exception) - || $this->handleInitException($exception) - || $this->handleGenericReport($bootstrap, $exception); - return $result; - } - - /** - * Error handler for developer mode - * - * @param Bootstrap $bootstrap - * @param \Exception $exception - * @return bool - */ - private function handleDeveloperMode(Bootstrap $bootstrap, \Exception $exception) + public function catchException(Bootstrap $bootstrap, \Exception $exception): bool { - if ($bootstrap->isDeveloperMode()) { - if (Bootstrap::ERR_IS_INSTALLED == $bootstrap->getErrorCode()) { - try { - $this->redirectToSetup($bootstrap, $exception); - return true; - } catch (\Exception $e) { - $exception = $e; - } - } - $this->_response->setHttpResponseCode(500); - $this->_response->setHeader('Content-Type', 'text/plain'); - $this->_response->setBody($this->buildContentFromException($exception)); - $this->_response->sendResponse(); - return true; - } - return false; - } - - /** - * Build content based on an exception - * - * @param \Exception $exception - * @return string - */ - private function buildContentFromException(\Exception $exception) - { - /** @var \Exception[] $exceptions */ - $exceptions = []; - - do { - $exceptions[] = $exception; - } while ($exception = $exception->getPrevious()); - - $buffer = sprintf("%d exception(s):\n", count($exceptions)); - - foreach ($exceptions as $index => $exception) { - $buffer .= sprintf("Exception #%d (%s): %s\n", $index, get_class($exception), $exception->getMessage()); - } - - foreach ($exceptions as $index => $exception) { - $buffer .= sprintf( - "\nException #%d (%s): %s\n%s\n", - $index, - get_class($exception), - $exception->getMessage(), - Debug::trace( - $exception->getTrace(), - true, - true, - (bool)getenv('MAGE_DEBUG_SHOW_ARGS') - ) - ); - } - - return $buffer; - } - - /** - * If not installed, try to redirect to installation wizard - * - * @param Bootstrap $bootstrap - * @param \Exception $exception - * @return void - * @throws \Exception - */ - private function redirectToSetup(Bootstrap $bootstrap, \Exception $exception) - { - $setupInfo = new SetupInfo($bootstrap->getParams()); - $projectRoot = $this->_filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath(); - if ($setupInfo->isAvailable()) { - $this->_response->setRedirect($setupInfo->getUrl()); - $this->_response->sendHeaders(); - } else { - $newMessage = $exception->getMessage() . "\nNOTE: You cannot install Magento using the Setup Wizard " - . "because the Magento setup directory cannot be accessed. \n" - . 'You can install Magento using either the command line or you must restore access ' - . 'to the following directory: ' . $setupInfo->getDir($projectRoot) . "\n"; - // phpcs:ignore Magento2.Exceptions.DirectThrow - throw new \Exception($newMessage, 0, $exception); - } - } - - /** - * Handler for bootstrap errors - * - * @param Bootstrap $bootstrap - * @param \Exception $exception - * @return bool - */ - private function handleBootstrapErrors(Bootstrap $bootstrap, \Exception &$exception) - { - $bootstrapCode = $bootstrap->getErrorCode(); - if (Bootstrap::ERR_MAINTENANCE == $bootstrapCode) { - // phpcs:ignore Magento2.Security.IncludeFile - require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/503.php'); - return true; - } - if (Bootstrap::ERR_IS_INSTALLED == $bootstrapCode) { - try { - $this->redirectToSetup($bootstrap, $exception); - return true; - } catch (\Exception $e) { - $exception = $e; - } - } - return false; - } - - /** - * Handler for session errors - * - * @param \Exception $exception - * @return bool - */ - private function handleSessionException(\Exception $exception) - { - if ($exception instanceof \Magento\Framework\Exception\SessionException) { - $this->_response->setRedirect($this->_request->getDistroBaseUrl()); - $this->_response->sendHeaders(); - return true; - } - return false; - } - - /** - * Handler for application initialization errors - * - * @param \Exception $exception - * @return bool - */ - private function handleInitException(\Exception $exception) - { - if ($exception instanceof \Magento\Framework\Exception\State\InitException) { - $this->getLogger()->critical($exception); - // phpcs:ignore Magento2.Security.IncludeFile - require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/404.php'); - return true; - } - return false; - } - - /** - * Handle for any other errors - * - * @param Bootstrap $bootstrap - * @param \Exception $exception - * @return bool - */ - private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception) - { - $reportData = [ - $exception->getMessage(), - Debug::trace( - $exception->getTrace(), - true, - true, - (bool)getenv('MAGE_DEBUG_SHOW_ARGS') - ) - ]; - $params = $bootstrap->getParams(); - if (isset($params['REQUEST_URI'])) { - $reportData['url'] = $params['REQUEST_URI']; - } - if (isset($params['SCRIPT_NAME'])) { - $reportData['script_name'] = $params['SCRIPT_NAME']; - } - // phpcs:ignore Magento2.Security.IncludeFile - require $this->_filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/report.php'); - return true; + return $this->exceptionHandler->handle($bootstrap, $exception, $this->_response, $this->_request); } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php b/lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php new file mode 100644 index 0000000000000..7ade77ee40df5 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php @@ -0,0 +1,277 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\App\Test\Unit; + +use Magento\Framework\App\ExceptionHandler; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\SetupInfo; +use Magento\Framework\App\Bootstrap; +use Magento\Framework\Debug; +use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\Filesystem; +use PHPUnit\Framework\MockObject\MockObject; +use Magento\Framework\App\Response\Http as ResponseHttp; +use Magento\Framework\App\Request\Http as RequestHttp; +use Psr\Log\LoggerInterface; +use Magento\Framework\Filesystem\Directory\ReadInterface; +use Magento\Framework\Exception\SessionException; +use Magento\Framework\Phrase; +use PHPUnit\Framework\Constraint\StringStartsWith; +use Magento\Framework\Exception\State\InitException; + +/** + * Test for \Magento\Framework\App\ExceptionHandler class + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ExceptionHandlerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ExceptionHandler + */ + private $exceptionHandler; + + /** + * @var EncryptorInterface|MockObject + */ + private $encryptorInterfaceMock; + + /** + * @var FileSystem|MockObject + */ + private $filesystemMock; + + /** + * @var LoggerInterface|MockObject + */ + protected $loggerMock; + + /** + * @var ResponseHttp|MockObject + */ + protected $responseMock; + + /** + * @var RequestHttp|MockObject + */ + protected $requestMock; + + protected function setUp() + { + $this->encryptorInterfaceMock = $this->createMock(EncryptorInterface::class); + $this->filesystemMock = $this->createMock(Filesystem::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); + $this->responseMock = $this->createMock(ResponseHttp::class); + $this->requestMock = $this->getMockBuilder(RequestHttp::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->exceptionHandler = new ExceptionHandler( + $this->encryptorInterfaceMock, + $this->filesystemMock, + $this->loggerMock + ); + } + + public function testHandleDeveloperModeNotInstalled() + { + $dir = $this->getMockForAbstractClass(ReadInterface::class); + $dir->expects($this->once()) + ->method('getAbsolutePath') + ->willReturn(__DIR__); + $this->filesystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->with(DirectoryList::ROOT) + ->willReturn($dir); + $this->responseMock->expects($this->once()) + ->method('setRedirect') + ->with('/_files/'); + $this->responseMock->expects($this->once()) + ->method('sendHeaders'); + $bootstrap = $this->getBootstrapNotInstalled(); + $bootstrap->expects($this->once()) + ->method('getParams') + ->willReturn( + [ + 'SCRIPT_NAME' => '/index.php', + 'DOCUMENT_ROOT' => __DIR__, + 'SCRIPT_FILENAME' => __DIR__ . '/index.php', + SetupInfo::PARAM_NOT_INSTALLED_URL_PATH => '_files', + ] + ); + $this->assertTrue( + $this->exceptionHandler->handle( + $bootstrap, + new \Exception('Test Message'), + $this->responseMock, + $this->requestMock + ) + ); + } + + public function testHandleDeveloperMode() + { + $this->filesystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->will($this->throwException(new \Exception('strange error'))); + $this->responseMock->expects($this->once()) + ->method('setHttpResponseCode') + ->with(500); + $this->responseMock->expects($this->once()) + ->method('setHeader') + ->with('Content-Type', 'text/plain'); + $constraint = new StringStartsWith('1 exception(s):'); + $this->responseMock->expects($this->once()) + ->method('setBody') + ->with($constraint); + $this->responseMock->expects($this->once()) + ->method('sendResponse'); + $bootstrap = $this->getBootstrapNotInstalled(); + $bootstrap->expects($this->once()) + ->method('getParams') + ->willReturn( + ['DOCUMENT_ROOT' => 'something', 'SCRIPT_FILENAME' => 'something/else'] + ); + $this->assertTrue( + $this->exceptionHandler->handle( + $bootstrap, + new \Exception('Test'), + $this->responseMock, + $this->requestMock + ) + ); + } + + public function testCatchExceptionSessionException() + { + $this->responseMock->expects($this->once()) + ->method('setRedirect'); + $this->responseMock->expects($this->once()) + ->method('sendHeaders'); + /** @var Bootstrap|MockObject $bootstrap */ + $bootstrap = $this->createMock(Bootstrap::class); + $bootstrap->expects($this->once()) + ->method('isDeveloperMode') + ->willReturn(false); + $this->assertTrue( + $this->exceptionHandler->handle( + $bootstrap, + new SessionException(new Phrase('Test')), + $this->responseMock, + $this->requestMock + ) + ); + } + + public function testHandleInitException() + { + $bootstrap = $this->getBootstrapInstalled(); + $exception = new InitException(new Phrase('Test')); + $dir = $this->getMockForAbstractClass(ReadInterface::class); + $dir->expects($this->once()) + ->method('getAbsolutePath') + ->with('errors/404.php') + ->willReturn(__DIR__ . '/_files/pub/errors/404.php'); + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($exception); + $this->filesystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->with(DirectoryList::PUB) + ->willReturn($dir); + + $this->assertTrue( + $this->exceptionHandler->handle( + $bootstrap, + $exception, + $this->responseMock, + $this->requestMock + ) + ); + } + + public function testHandleGenericReport() + { + $bootstrap = $this->getBootstrapInstalled(); + $exception = new \Exception('Test'); + $dir = $this->getMockForAbstractClass(ReadInterface::class); + $dir->expects($this->once()) + ->method('getAbsolutePath') + ->with('errors/report.php') + ->willReturn(__DIR__ . '/_files/pub/errors/report.php'); + $bootstrap->expects($this->once()) + ->method('getParams') + ->willReturn(['REQUEST_URI' => 'some-request-uri', 'SCRIPT_NAME' => 'some-script-name']); + $reportData = [ + $exception->getMessage(), + Debug::trace( + $exception->getTrace(), + true, + false, + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') + ), + 'url' => 'some-request-uri', + 'script_name' => 'some-script-name' + ]; + $this->encryptorInterfaceMock->expects($this->once()) + ->method('getHash') + ->with(implode('', $reportData)) + ->willReturn('some-sha256-hash'); + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($exception, ['report_id' => 'some-sha256-hash']); + $this->filesystemMock->expects($this->once()) + ->method('getDirectoryRead') + ->with(DirectoryList::PUB) + ->willReturn($dir); + + $this->assertTrue( + $this->exceptionHandler->handle( + $bootstrap, + $exception, + $this->responseMock, + $this->requestMock + ) + ); + } + + /** + * Prepares a mock of bootstrap in "not installed" state + * + * @return Bootstrap|MockObject + */ + private function getBootstrapNotInstalled(): Bootstrap + { + $bootstrap = $this->createMock(Bootstrap::class); + $bootstrap->expects($this->once()) + ->method('isDeveloperMode') + ->willReturn(true); + $bootstrap->expects($this->once()) + ->method('getErrorCode') + ->willReturn(Bootstrap::ERR_IS_INSTALLED); + return $bootstrap; + } + + /** + * Prepares a mock of bootstrap in "installed" state + * + * @return Bootstrap|MockObject + */ + private function getBootstrapInstalled(): Bootstrap + { + /** @var Bootstrap|MockObject $bootstrap */ + $bootstrap = $this->createMock(Bootstrap::class); + $bootstrap->expects($this->once()) + ->method('isDeveloperMode') + ->willReturn(false); + $bootstrap->expects($this->once()) + ->method('getErrorCode') + ->willReturn(0); + return $bootstrap; + } +} diff --git a/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php b/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php index dbb315e88a526..6b5f11b21b9be 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php @@ -3,13 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Framework\App\Test\Unit; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\App\SetupInfo; -use Magento\Framework\App\Bootstrap; - /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -63,7 +58,7 @@ class HttpTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $filesystemMock; + protected $exceptionHandlerMock; protected function setUp() { @@ -85,13 +80,15 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->setConstructorArgs([ - 'cookieReader' => $cookieReaderMock, - 'converter' => $converterMock, - 'routeConfig' => $routeConfigMock, - 'pathInfoProcessor' => $pathInfoProcessorMock, - 'objectManager' => $objectManagerMock - ]) + ->setConstructorArgs( + [ + 'cookieReader' => $cookieReaderMock, + 'converter' => $converterMock, + 'routeConfig' => $routeConfigMock, + 'pathInfoProcessor' => $pathInfoProcessorMock, + 'objectManager' => $objectManagerMock + ] + ) ->setMethods(['getFrontName', 'isHead']) ->getMock(); $this->areaListMock = $this->getMockBuilder(\Magento\Framework\App\AreaList::class) @@ -112,7 +109,7 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['dispatch']) ->getMock(); - $this->filesystemMock = $this->createMock(\Magento\Framework\Filesystem::class); + $this->exceptionHandlerMock = $this->createMock(\Magento\Framework\App\ExceptionHandlerInterface::class); $this->http = $this->objectManager->getObject( \Magento\Framework\App\Http::class, @@ -123,7 +120,7 @@ protected function setUp() 'request' => $this->requestMock, 'response' => $this->responseMock, 'configLoader' => $this->configLoaderMock, - 'filesystem' => $this->filesystemMock, + 'exceptionHandler' => $this->exceptionHandlerMock, ] ); } @@ -247,92 +244,4 @@ public function dataProviderForTestLaunchHeadRequest(): array ] ]; } - - public function testHandleDeveloperModeNotInstalled() - { - $dir = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\ReadInterface::class); - $dir->expects($this->once()) - ->method('getAbsolutePath') - ->willReturn(__DIR__); - $this->filesystemMock->expects($this->once()) - ->method('getDirectoryRead') - ->with(DirectoryList::ROOT) - ->willReturn($dir); - $this->responseMock->expects($this->once()) - ->method('setRedirect') - ->with('/_files/'); - $this->responseMock->expects($this->once()) - ->method('sendHeaders'); - $bootstrap = $this->getBootstrapNotInstalled(); - $bootstrap->expects($this->once()) - ->method('getParams') - ->willReturn( - [ - 'SCRIPT_NAME' => '/index.php', - 'DOCUMENT_ROOT' => __DIR__, - 'SCRIPT_FILENAME' => __DIR__ . '/index.php', - SetupInfo::PARAM_NOT_INSTALLED_URL_PATH => '_files', - ] - ); - $this->assertTrue($this->http->catchException($bootstrap, new \Exception('Test Message'))); - } - - public function testHandleDeveloperMode() - { - $this->filesystemMock->expects($this->once()) - ->method('getDirectoryRead') - ->will($this->throwException(new \Exception('strange error'))); - $this->responseMock->expects($this->once()) - ->method('setHttpResponseCode') - ->with(500); - $this->responseMock->expects($this->once()) - ->method('setHeader') - ->with('Content-Type', 'text/plain'); - $constraint = new \PHPUnit\Framework\Constraint\StringStartsWith('1 exception(s):'); - $this->responseMock->expects($this->once()) - ->method('setBody') - ->with($constraint); - $this->responseMock->expects($this->once()) - ->method('sendResponse'); - $bootstrap = $this->getBootstrapNotInstalled(); - $bootstrap->expects($this->once()) - ->method('getParams') - ->willReturn( - ['DOCUMENT_ROOT' => 'something', 'SCRIPT_FILENAME' => 'something/else'] - ); - $this->assertTrue($this->http->catchException($bootstrap, new \Exception('Test'))); - } - - public function testCatchExceptionSessionException() - { - $this->responseMock->expects($this->once()) - ->method('setRedirect'); - $this->responseMock->expects($this->once()) - ->method('sendHeaders'); - $bootstrap = $this->createMock(\Magento\Framework\App\Bootstrap::class); - $bootstrap->expects($this->once()) - ->method('isDeveloperMode') - ->willReturn(false); - $this->assertTrue($this->http->catchException( - $bootstrap, - new \Magento\Framework\Exception\SessionException(new \Magento\Framework\Phrase('Test')) - )); - } - - /** - * Prepares a mock of bootstrap in "not installed" state - * - * @return \PHPUnit_Framework_MockObject_MockObject - */ - private function getBootstrapNotInstalled() - { - $bootstrap = $this->createMock(\Magento\Framework\App\Bootstrap::class); - $bootstrap->expects($this->once()) - ->method('isDeveloperMode') - ->willReturn(true); - $bootstrap->expects($this->once()) - ->method('getErrorCode') - ->willReturn(Bootstrap::ERR_IS_INSTALLED); - return $bootstrap; - } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/404.php b/lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/404.php new file mode 100644 index 0000000000000..37121092d0ba0 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/404.php @@ -0,0 +1,6 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); diff --git a/lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/report.php b/lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/report.php new file mode 100644 index 0000000000000..37121092d0ba0 --- /dev/null +++ b/lib/internal/Magento/Framework/App/Test/Unit/_files/pub/errors/report.php @@ -0,0 +1,6 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); diff --git a/pub/errors/local.xml.sample b/pub/errors/local.xml.sample index b89dbb3fee8f5..bdd980d4f8a2c 100644 --- a/pub/errors/local.xml.sample +++ b/pub/errors/local.xml.sample @@ -27,5 +27,22 @@ value "delete" is for cleaning --> <trash>leave</trash> + <!-- + The number of subdirectories that will be created to save the report. + Valid Values: Integers from 0 to 32 + + Example: + If we have the report name as hash sha256('') = 44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7 + + dir_nesting_level=0 -> <magento_root>/var/report/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7 + dir_nesting_level=1 -> <magento_root>/var/report/44/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7 + dir_nesting_level=2 -> <magento_root>/var/report/44/ff/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7 + ... + dir_nesting_level=32 -> <magento_root>/var/report/44/ff/b1/08/7a/44/e6/1b/01/8b/3c/de/e7/22/84/d0/17/f2/2e/52/75/5c/24/e5/c8/5c/ba/c1/64/7a/e7/a7/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7 + + If you use an environment variable MAGE_LOG_REPORT_DIR_NESTING_LEVEL, this property will be ignored. + Environment variable has a higher priority. + --> + <dir_nesting_level>0</dir_nesting_level> </report> </config> diff --git a/pub/errors/processor.php b/pub/errors/processor.php index ab21f791bc021..63d054319fb07 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -8,11 +8,15 @@ namespace Magento\Framework\Error; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Escaper; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Response\Http; /** * Error processor * * @SuppressWarnings(PHPMD.TooManyFields) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * phpcs:ignoreFile */ class Processor @@ -21,6 +25,7 @@ class Processor const MAGE_ERRORS_DESIGN_XML = 'design.xml'; const DEFAULT_SKIN = 'default'; const ERROR_DIR = 'pub/errors'; + const NUMBER_SYMBOLS_IN_SUBDIR_NAME = 2; /** * Page title @@ -67,7 +72,7 @@ class Processor /** * Report ID * - * @var int + * @var string */ public $reportId; @@ -128,7 +133,7 @@ class Processor /** * Http response * - * @var \Magento\Framework\App\Response\Http + * @var Http */ protected $_response; @@ -140,15 +145,25 @@ class Processor private $serializer; /** - * @param \Magento\Framework\App\Response\Http $response + * @var Escaper + */ + private $escaper; + + /** + * @param Http $response * @param Json $serializer + * @param Escaper $escaper */ - public function __construct(\Magento\Framework\App\Response\Http $response, Json $serializer = null) - { + public function __construct( + Http $response, + Json $serializer = null, + Escaper $escaper = null + ) { $this->_response = $response; $this->_errorDir = __DIR__ . '/'; $this->_reportDir = dirname(dirname($this->_errorDir)) . '/var/report/'; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()->get(Json::class); + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); if (!empty($_SERVER['SCRIPT_NAME'])) { if (in_array(basename($_SERVER['SCRIPT_NAME'], '.php'), ['404', '503', 'report'])) { @@ -158,11 +173,6 @@ public function __construct(\Magento\Framework\App\Response\Http $response, Json } } - $reportId = (isset($_GET['id'])) ? (int)$_GET['id'] : null; - if ($reportId) { - $this->loadReport($reportId); - } - $this->_indexDir = $this->_getIndexDir(); $this->_root = is_dir($this->_indexDir . 'app'); @@ -170,6 +180,9 @@ public function __construct(\Magento\Framework\App\Response\Http $response, Json if (isset($_GET['skin'])) { $this->_setSkin($_GET['skin']); } + if (isset($_GET['id'])) { + $this->loadReport($_GET['id']); + } } /** @@ -371,6 +384,9 @@ protected function _prepareConfig() if ((string)$local->report->trash) { $config->trash = $local->report->trash; } + if ($local->report->dir_nesting_level) { + $config->dir_nesting_level = (int)$local->report->dir_nesting_level; + } if ((string)$local->skin) { $this->_setSkin((string)$local->skin, $config); } @@ -467,7 +483,7 @@ protected function _setReportData($reportData) $this->reportData['url'] = $this->getHostUrl() . $reportData['url']; } - if ($this->reportData['script_name']) { + if (isset($this->reportData['script_name'])) { $this->_scriptName = $this->reportData['script_name']; } } @@ -478,17 +494,18 @@ protected function _setReportData($reportData) * @param array $reportData * @return string */ - public function saveReport($reportData) + public function saveReport(array $reportData): string { - $this->reportData = $reportData; - $this->reportId = abs((int)(microtime(true) * random_int(100, 1000))); - $this->_reportFile = $this->_reportDir . '/' . $this->reportId; - $this->_setReportData($reportData); - - if (!file_exists($this->_reportDir)) { - @mkdir($this->_reportDir, 0777, true); + $this->reportId = $reportData['report_id']; + $this->_reportFile = $this->getReportPath( + $this->getReportDirNestingLevel($this->reportId), + $this->reportId + ); + $reportDirName = dirname($this->_reportFile); + if (!file_exists($reportDirName)) { + @mkdir($reportDirName, 0777, true); } - + $this->_setReportData($reportData); @file_put_contents($this->_reportFile, $this->serializer->serialize($reportData)); if (isset($reportData['skin']) && self::DEFAULT_SKIN != $reportData['skin']) { @@ -502,19 +519,117 @@ public function saveReport($reportData) /** * Get report * - * @param int $reportId + * @param string $reportId * @return void */ public function loadReport($reportId) { - $this->reportId = $reportId; - $this->_reportFile = $this->_reportDir . '/' . $reportId; + try { + if (!$this->isReportIdValid($reportId)) { + throw new \RuntimeException("Report Id is invalid"); + } + $reportFile = $this->findReportFile($reportId); + if (!is_readable($reportFile)) { + throw new \RuntimeException("Report file cannot be read"); + } + $this->reportId = $reportId; + $this->_reportFile = $reportFile; + $this->_setReportData($this->serializer->unserialize(file_get_contents($this->_reportFile))); + } catch (\RuntimeException $e) { + $this->redirectToBaseUrl(); + } + } - if (!file_exists($this->_reportFile) || !is_readable($this->_reportFile)) { - header("Location: " . $this->getBaseUrl()); - die(); + /** + * Searches for the report file and returns the path to it + * + * @param string $reportId + * @return string + * @throws \RuntimeException + */ + private function findReportFile(string $reportId): string + { + $reportFile = $this->getReportPath( + $this->getReportDirNestingLevel($reportId), + $reportId + ); + if (file_exists($reportFile)) { + return $reportFile; + } + $maxReportDirNestingLevel = $this->getMaxReportDirNestingLevel($reportId); + for ($i = 0; $i <= $maxReportDirNestingLevel; $i++) { + $reportFile = $this->getReportPath($i, $reportId); + if (file_exists($reportFile)) { + return $reportFile; + } } - $this->_setReportData($this->serializer->unserialize(file_get_contents($this->_reportFile))); + throw new \RuntimeException("Report file not found"); + } + + /** + * Redirect to a base url + * @return void + */ + private function redirectToBaseUrl() + { + header("Location: " . $this->getBaseUrl()); + die(); + } + + /** + * Checks report id + * + * @param string $reportId + * @return bool + */ + private function isReportIdValid(string $reportId): bool + { + return (bool)preg_match('/[a-fA-F0-9]{64}/', $reportId); + } + + /** + * Get path to reports + * + * @param integer $reportDirNestingLevel + * @param string $reportId + * @return string + */ + private function getReportPath(int $reportDirNestingLevel, string $reportId): string + { + $reportDirPath = $this->_reportDir; + for ($i = 0, $j = 0; $j < $reportDirNestingLevel; $i += 2, $j++) { + $reportDirPath .= $reportId[$i] . $reportId[$i + 1] . '/'; + } + return $reportDirPath . $reportId; + } + + /** + * Returns nesting Level for the report files + * + * @var $reportId + * @return int + */ + private function getReportDirNestingLevel(string $reportId): int + { + $envName = 'MAGE_LOG_REPORT_DIR_NESTING_LEVEL'; + $value = $_ENV[$envName] ?? getenv($envName); + if(false === $value && property_exists($this->_config, 'dir_nesting_level')) { + $value = $this->_config->dir_nesting_level; + } + $value = (int)$value; + $maxValue= $this->getMaxReportDirNestingLevel($reportId); + return 0 < $value && $maxValue >= $value ? $value : 0; + } + + /** + * Returns maximum nesting level directories of report files + * + * @param string $reportId + * @return integer + */ + private function getMaxReportDirNestingLevel(string $reportId): int + { + return (integer)floor(mb_strlen($reportId) / self::NUMBER_SYMBOLS_IN_SUBDIR_NAME); } /** @@ -528,11 +643,16 @@ public function sendReport() { $this->pageTitle = 'Error Submission Form'; - $this->postData['firstName'] = (isset($_POST['firstname'])) ? trim(htmlspecialchars($_POST['firstname'])) : ''; - $this->postData['lastName'] = (isset($_POST['lastname'])) ? trim(htmlspecialchars($_POST['lastname'])) : ''; - $this->postData['email'] = (isset($_POST['email'])) ? trim(htmlspecialchars($_POST['email'])) : ''; - $this->postData['telephone'] = (isset($_POST['telephone'])) ? trim(htmlspecialchars($_POST['telephone'])) : ''; - $this->postData['comment'] = (isset($_POST['comment'])) ? trim(htmlspecialchars($_POST['comment'])) : ''; + $this->postData['firstName'] = (isset($_POST['firstname'])) + ? trim($this->escaper->escapeHtml($_POST['firstname'])) : ''; + $this->postData['lastName'] = (isset($_POST['lastname'])) + ? trim($this->escaper->escapeHtml($_POST['lastname'])) : ''; + $this->postData['email'] = (isset($_POST['email'])) + ? trim($this->escaper->escapeHtml($_POST['email'])) : ''; + $this->postData['telephone'] = (isset($_POST['telephone'])) + ? trim($this->escaper->escapeHtml($_POST['telephone'])) : ''; + $this->postData['comment'] = (isset($_POST['comment'])) + ? trim($this->escaper->escapeHtml($_POST['comment'])) : ''; if (isset($_POST['submit'])) { if ($this->_validate()) { From 940a6a4edabf64e1575b362d77c9c1f8855cc61d Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Tue, 10 Sep 2019 14:38:41 -0500 Subject: [PATCH 738/841] Stabilize multishipping test with Magento inventory --- .../Multishipping/Fixtures/quote_with_configurable_product.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php b/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php index df6854aed1971..2a472371fd19f 100644 --- a/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php +++ b/dev/tests/integration/testsuite/Magento/Multishipping/Fixtures/quote_with_configurable_product.php @@ -22,7 +22,7 @@ $objectManager = Bootstrap::getObjectManager(); $product = $productRepository->getById(10); -$product->setStockData(['use_config_manage_stock' => 1, 'qty' => 2, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); +$product->setStockData(['use_config_manage_stock' => 1, 'qty' => 4, 'is_qty_decimal' => 0, 'is_in_stock' => 1]); $productRepository->save($product); /** @var Quote $quote */ From 8911e4290d8087bb11741790810bcddef333c4ff Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Tue, 10 Sep 2019 12:00:26 -0500 Subject: [PATCH 739/841] MC-19866: Redundant attributes inside CMS widget body - Fix wrong parameters in widget declaration --- lib/web/mage/adminhtml/wysiwyg/widget.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/web/mage/adminhtml/wysiwyg/widget.js b/lib/web/mage/adminhtml/wysiwyg/widget.js index 5c1a77b6382a2..f39fc22034104 100644 --- a/lib/web/mage/adminhtml/wysiwyg/widget.js +++ b/lib/web/mage/adminhtml/wysiwyg/widget.js @@ -223,7 +223,9 @@ define([ * @param {*} containerId */ enableOptionsContainer: function (containerId) { - $$('#' + containerId + ' .widget-option').each(function (e) { + var container = $(containerId); + + container.select('.widget-option').each(function (e) { e.removeClassName('skip-submit'); if (e.hasClassName('obligatory')) { @@ -231,18 +233,19 @@ define([ e.addClassName('required-entry'); } }); - $(containerId).removeClassName('no-display'); + container.removeClassName('no-display'); }, /** * @param {*} containerId */ disableOptionsContainer: function (containerId) { + var container = $(containerId); - if ($(containerId).hasClassName('no-display')) { + if (container.hasClassName('no-display')) { return; } - $$('#' + containerId + ' .widget-option').each(function (e) { + container.select('.widget-option').each(function (e) { // Avoid submitting fields of unactive container if (!e.hasClassName('skip-submit')) { e.addClassName('skip-submit'); @@ -253,7 +256,7 @@ define([ e.addClassName('obligatory'); } }); - $(containerId).addClassName('no-display'); + container.addClassName('no-display'); }, /** @@ -439,7 +442,7 @@ define([ i = 0; Form.getElements($(this.formEl)).each(function (e) { - if (!e.hasClassName('skip-submit')) { + if (jQuery(e).closest('.skip-submit, .no-display').length === 0) { formElements[i] = e; i++; } From d6e97e39bc4f949197d920f6db257e9ee0ad4f73 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Tue, 10 Sep 2019 17:21:31 -0500 Subject: [PATCH 740/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - added test coverage for filtering by url_key --- .../GraphQl/Catalog/ProductSearchTest.php | 199 ++++++++++++++++++ 1 file changed, 199 insertions(+) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index e7f3655984b26..bd44a4678de53 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -701,6 +701,205 @@ public function testFilterByCategoryIdAndCustomAttribute() } } + /** + * Filter by exact match of product url key + * + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testFilterBySingleProductUrlKey() + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + /** @var Product $product */ + $product = $productRepository->get('simple-4'); + $urlKey = $product->getUrlKey(); + + $query = <<<QUERY +{ + products(filter:{ + url_key:{eq:"{$urlKey}"} + } + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + url_key + } + page_info{ + current_page + page_size + total_pages + } + filters{ + name + request_var + filter_items_count + filter_items{ + label + items_count + value_string + __typename + } + } + aggregations + { + attribute_code + count + label + options + { + count + label + value + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(1, $response['products']['total_count'], 'More than 1 product found'); + $this->assertCount(2, $response['products']['aggregations']); + $this->assertResponseFields($response['products']['items'][0], + [ + 'name' => $product->getName(), + 'sku' => $product->getSku(), + 'url_key'=> $product->getUrlKey() + ] + ); + $this->assertEquals('Price', $response['products']['aggregations'][0]['label']); + $this->assertEquals('Category', $response['products']['aggregations'][1]['label']); + //Disable the product + $product->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED); + $productRepository->save($product); + $query2 = <<<QUERY +{ + products(filter:{ + url_key:{eq:"{$urlKey}"} + } + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + url_key + } + + filters{ + name + request_var + filter_items_count + } + aggregations + { + attribute_code + count + label + options + { + count + label + value + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query2); + $this->assertEquals(0, $response['products']['total_count'], 'Total count should be zero'); + $this->assertEmpty($response['products']['items']); + $this->assertEmpty($response['products']['aggregations']); + } + + /** + * Filter by multiple product url keys + * + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testFilterByMultipleProductUrlKeys() + { + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + /** @var Product $product */ + $product1 = $productRepository->get('simple'); + $product2 = $productRepository->get('12345'); + $product3 = $productRepository->get('simple-4'); + $filteredProducts = [$product1, $product2, $product3]; + $urlKey =[]; + foreach ($filteredProducts as $product) { + $urlKey[] = $product->getUrlKey(); + } + + $query = <<<QUERY +{ + products(filter:{ + url_key:{in:["{$urlKey[0]}", "{$urlKey[1]}", "{$urlKey[2]}"]} + } + pageSize: 3 + currentPage: 1 + ) + { + total_count + items + { + name + sku + url_key + } + page_info{ + current_page + page_size + + } + filters{ + name + request_var + filter_items_count + } + aggregations + { + attribute_code + count + label + options + { + count + label + value + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals(3, $response['products']['total_count'], 'Total count is incorrect'); + $this->assertCount(2, $response['products']['aggregations']); + + $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); + //phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall + for ($itemIndex = 0; $itemIndex < count($filteredProducts); $itemIndex++) { + $this->assertNotEmpty($productItemsInResponse[$itemIndex]); + //validate that correct products are returned + $this->assertResponseFields( + $productItemsInResponse[$itemIndex][0], + [ 'name' => $filteredProducts[$itemIndex]->getName(), + 'sku' => $filteredProducts[$itemIndex]->getSku(), + 'url_key'=> $filteredProducts[$itemIndex]->getUrlKey() + ] + ); + } + } + /** * Get array with expected data for layered navigation filters * From b8d6ad053b1c11fbabc2ee6d091a0fcc5264aa91 Mon Sep 17 00:00:00 2001 From: Ravi Chandra <ravi.chandra@krishtechnolabs.com> Date: Wed, 11 Sep 2019 10:43:22 +0530 Subject: [PATCH 741/841] Add digits validation for Bulk Actions,Full Page Cache(TTL), Environment Update Time system configuration --- app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml | 1 + app/code/Magento/MediaStorage/etc/adminhtml/system.xml | 1 + app/code/Magento/PageCache/etc/adminhtml/system.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml b/app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml index 7190b80750357..e373a4fc78b13 100644 --- a/app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml +++ b/app/code/Magento/AsynchronousOperations/etc/adminhtml/system.xml @@ -13,6 +13,7 @@ <label>Bulk Actions</label> <field id="lifetime" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Days Saved in Log</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> </group> </section> diff --git a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml index 0f6e7f93aea11..2c7219fe8afaa 100644 --- a/app/code/Magento/MediaStorage/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaStorage/etc/adminhtml/system.xml @@ -28,6 +28,7 @@ </field> <field id="configuration_update_time" translate="label" type="text" sortOrder="400" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Environment Update Time</label> + <validate>validate-zero-or-greater validate-digits</validate> </field> </group> </section> diff --git a/app/code/Magento/PageCache/etc/adminhtml/system.xml b/app/code/Magento/PageCache/etc/adminhtml/system.xml index 8013ad40ef5aa..234e3e48a95d8 100644 --- a/app/code/Magento/PageCache/etc/adminhtml/system.xml +++ b/app/code/Magento/PageCache/etc/adminhtml/system.xml @@ -74,6 +74,7 @@ </group> <field id="ttl" type="text" translate="label comment" sortOrder="5" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>TTL for public content</label> + <validate>validate-zero-or-greater validate-digits</validate> <comment>Public content cache lifetime in seconds. If field is empty default value 86400 will be saved. </comment> <backend_model>Magento\PageCache\Model\System\Config\Backend\Ttl</backend_model> </field> From 62c216cc99c443fe0bd4f35a556cacc88f770a07 Mon Sep 17 00:00:00 2001 From: rani-webkul <rani.priya4392webkul.com> Date: Tue, 27 Aug 2019 19:33:40 +0530 Subject: [PATCH 742/841] fixed File type custom option value not showing properly on wish list page #24319 fixed wishlist custom option download issue made required changes removed rootDirBasePath from constructor argument removed unnecessary arguments --- .../Magento/Wishlist/Block/Customer/Wishlist/Item/Options.php | 2 +- .../Magento/Wishlist/Controller/Index/DownloadCustomOption.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Options.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Options.php index 63a480801d5d4..82133927e1201 100644 --- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Options.php +++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Options.php @@ -114,7 +114,7 @@ public function getConfiguredOptions() $option['value'][$key] = $this->escapeHtml($value); } } else { - $option['value'] = $this->escapeHtml($option['value']); + $option['value'] = $this->escapeHtml($option['value'], ["a"]); } } $options[$index]['value'] = $option['value']; diff --git a/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php b/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php index 742b2a91e9317..dc0ea8d5a093a 100644 --- a/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php +++ b/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php @@ -99,7 +99,7 @@ public function execute() $this->_fileResponseFactory->create( $info['title'], ['value' => $info['quote_path'], 'type' => 'filename'], - DirectoryList::ROOT, + DirectoryList::MEDIA, $info['type'] ); } From 20141ce952d7dd65af9e17dcde55c6e5609891f2 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Wed, 11 Sep 2019 10:54:47 +0300 Subject: [PATCH 743/841] MC-20109: [MFTF] StorefrontVerifySearchSuggestionByProductDescriptionTest is flaky --- .../AdminProductGridActionGroup.xml | 19 +++++++++++++++++++ ...archSuggestionByProductDescriptionTest.xml | 1 + 2 files changed, 20 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml index 6a260bbf22522..aef79e651b584 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -423,4 +423,23 @@ <click selector="{{AdminProductGridConfirmActionSection.ok}}" stepKey="confirmProductDelete"/> <waitForPageLoad stepKey="waitForGridLoad"/> </actionGroup> + + <actionGroup name="deleteAllProductsUsingProductGrid"> + <annotations> + <description>Deletes all products in Admin Products grid page.</description> + </annotations> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="openAdminGridProductsPage"/> + <waitForPageLoad time="60" stepKey="waitForPageFullyLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clearGridFilters"/> + + <conditionalClick selector="{{AdminProductGridSection.multicheckDropdown}}" dependentSelector="{{AdminDataGridTableSection.dataGridEmpty}}" visible="false" stepKey="openMulticheckDropdown"/> + <conditionalClick selector="{{AdminProductGridSection.multicheckOption('Select All')}}" dependentSelector="{{AdminDataGridTableSection.dataGridEmpty}}" visible="false" stepKey="selectAllProductsInGrid"/> + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Delete')}}" stepKey="clickDeleteAction"/> + + <waitForElementVisible selector="{{AdminConfirmationModalSection.message}}" stepKey="waitForConfirmModal"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete"/> + <waitForElementVisible selector="{{AdminDataGridTableSection.dataGridEmpty}}" stepKey="waitGridIsEmpty"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml index 0ec33c48f259e..61a89b4610d6a 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml @@ -28,6 +28,7 @@ <!-- Delete all search terms --> <comment userInput="Delete all search terms" stepKey="deleteAllSearchTermsComment"/> <actionGroup ref="DeleteAllSearchTerms" stepKey="deleteAllSearchTerms"/> + <actionGroup ref="deleteAllProductsUsingProductGrid" stepKey="deleteAllProducts"/> <!-- Create product with description --> <comment userInput="Create product with description" stepKey="createProductWithDescriptionComment"/> <createData entity="SimpleProductWithDescription" stepKey="simpleProduct"/> From dc2c7c07b80be22d116284b5f44f8a0475240a9d Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Wed, 11 Sep 2019 11:32:08 +0300 Subject: [PATCH 744/841] MC-6443: Product custom URL Key is preserved when assigned to a Category (without custom URL Key) alongside with another Product without custom URL Key --- .../ActionGroup/AdminCategoryActionGroup.xml | 18 +++ .../Section/AdminCategoryContentSection.xml | 1 + ...nAssignedToCategoryWithoutCustomURLKey.xml | 111 ++++++++++++++++++ .../AdminDeleteStoreViewActionGroup.xml | 1 + 4 files changed, 131 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 12bf5179a07d0..f2005219ffbc0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -396,4 +396,22 @@ <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> <waitForPageLoad stepKey="waitForPageToLoad1"/> </actionGroup> + + <actionGroup name="AdminCategoryAssignProduct"> + <annotations> + <description>Assign products to category - using "Products in Category" tab.</description> + </annotations> + <arguments> + <argument name="productSku" type="string"/> + </arguments> + + <conditionalClick selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" dependentSelector="{{CatalogProductsSection.resetFilter}}" visible="false" stepKey="clickOnProductInCategory"/> + <waitForPageLoad stepKey="waitForProductsInCategoryOpened"/> + <click selector="{{CatalogProductsSection.resetFilter}}" stepKey="clickOnResetFilter"/> + <waitForPageLoad stepKey="waitForProductsToLoad"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnSku}}" userInput="{{productSku}}" stepKey="selectProduct"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml index e3d224904671b..1cb095974d0fd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml @@ -24,5 +24,6 @@ <element name="productTableColumnName" type="input" selector="#catalog_category_products_filter_name"/> <element name="productTableRow" type="button" selector="#catalog_category_products_table tbody tr"/> <element name="productSearch" type="button" selector="//button[@data-action='grid-filter-apply']" timeout="30"/> + <element name="productTableColumnSku" type="input" selector="#catalog_category_products_filter_sku"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml new file mode 100644 index 0000000000000..d7a01ac0c8612 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml @@ -0,0 +1,111 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey"> + <annotations> + <stories value="Product"/> + <title value="Product custom URL Key is preserved when assigned to a Category (without custom URL Key) alongside with another Product without custom URL Key"/> + <description value="The test verifies that product custom URL Key is preserved when assigned to a Category (without custom URL Key) alongside with another Product without custom URL Key."/> + <severity value="MAJOR"/> + <testCaseId value="MC-6443"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Create Simple Products --> + <createData entity="SimpleProduct2" stepKey="createSimpleProductFirst"/> + <createData entity="SimpleProduct2" stepKey="createSimpleProductSecond"/> + + <!--Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" arguments="full_page" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createSimpleProductFirst" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createSimpleProductSecond" stepKey="deleteSecondSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open product --> + <amOnPage url="{{AdminProductEditPage.url($createSimpleProductSecond.id$)}}" stepKey="openProductSecondEditPage"/> + <!-- switch store view --> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchToStoreView"> + <argument name="storeView" value="storeViewData.name"/> + </actionGroup> + + <!-- set url key --> + <conditionalClick selector="{{AdminProductSEOSection.sectionHeader}}" dependentSelector="{{AdminProductSEOSection.urlKeyInput}}" visible="false" stepKey="openSeoSection"/> + <uncheckOption selector="{{AdminProductSEOSection.useDefaultUrl}}" stepKey="uncheckUseDefaultUrlKey"/> + <fillField userInput="U2" selector="{{AdminProductSEOSection.urlKeyInput}}" stepKey="fillUrlKey"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <actionGroup ref="goToAdminCategoryPageById" stepKey="openCategory"> + <argument name="id" value="$createCategory.id$"/> + </actionGroup> + + <actionGroup ref="AdminCategoryAssignProduct" stepKey="assignSimpleProductFirst"> + <argument name="productSku" value="$createSimpleProductFirst.sku$"/> + </actionGroup> + <actionGroup ref="AdminCategoryAssignProduct" stepKey="assignSimpleProductSecond"> + <argument name="productSku" value="$createSimpleProductSecond.sku$"/> + </actionGroup> + + <actionGroup ref="saveCategoryForm" stepKey="saveCategory"/> + + <executeJS function="var s = '$createCategory.name$'; var res=s.toLowerCase(); return res;" stepKey="categoryNameLower" /> + <executeJS function="var s = '$createSimpleProductFirst.name$'; var res=s.toLowerCase(); return res;" stepKey="simpleProductFirstNameLower" /> + <executeJS function="var s = '$createSimpleProductSecond.name$';var res=s.toLowerCase(); return res;" stepKey="simpleProductSecondNameLower" /> + + <!-- Make assertions on frontend --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($createCategory.name$)}}" stepKey="onCategoryPage"/> + <seeInCurrentUrl url="{$categoryNameLower}.html" stepKey="checkCategryUrlKey"/> + + <!-- Open first product --> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($createSimpleProductFirst.name$)}}" stepKey="openFirstProduct"/> + <seeInCurrentUrl url="{$simpleProductFirstNameLower}.html" stepKey="checkFirstSimpleProductUrlKey"/> + + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryView"/> + <!-- Open second product --> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($createSimpleProductSecond.name$)}}" stepKey="openSecondProduct"/> + <seeInCurrentUrl url="{$simpleProductSecondNameLower}.html" stepKey="checkSecondSimpleProductUrlKey"/> + + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchToCustomStoreView"> + <argument name="storeView" value="storeViewData"/> + </actionGroup> + + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($createCategory.name$)}}" stepKey="openCategoryPage"/> + <seeInCurrentUrl url="{$categoryNameLower}.html" stepKey="seeCategoryUrlKey"/> + + <!-- Open product first --> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSimpleProductFirst.name$$)}}" stepKey="openFirstSimpleProduct"/> + <seeInCurrentUrl url="{$simpleProductFirstNameLower}.html" stepKey="assertFirstSimpleProductUrlKey"/> + + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($createCategory.name$)}}" stepKey="openCategoryView"/> + <!-- Open product2 --> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($createSimpleProductSecond.name$)}}" stepKey="openSecondSimpleProduct"/> + <seeInCurrentUrl url="u2.html" stepKey="assertSecondSimpleProductUrlKey"/> + </test> +</tests> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml index 94856bb083da8..63dc4b0ded4f9 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml @@ -18,6 +18,7 @@ <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="navigateToStoresIndex"/> <waitForPageLoad stepKey="waitStoreIndexPageLoad"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> <fillField selector="{{AdminStoresGridSection.storeFilterTextField}}" userInput="{{customStore.name}}" stepKey="fillStoreViewFilterField"/> <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearch"/> <click selector="{{AdminStoresGridSection.storeNameInFirstRow}}" stepKey="clickStoreViewInGrid"/> From 93e93deb70e397b1a4e21a0714d9905a99ea127d Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 11 Sep 2019 12:16:03 +0300 Subject: [PATCH 745/841] MC-19701: Country code for Taiwan (Province of China) --- lib/internal/Magento/Framework/Locale/TranslatedLists.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/internal/Magento/Framework/Locale/TranslatedLists.php b/lib/internal/Magento/Framework/Locale/TranslatedLists.php index 2087564dcec20..1724198f15b9f 100644 --- a/lib/internal/Magento/Framework/Locale/TranslatedLists.php +++ b/lib/internal/Magento/Framework/Locale/TranslatedLists.php @@ -10,6 +10,7 @@ use Magento\Framework\Locale\Bundle\DataBundle; use Magento\Framework\Locale\Bundle\LanguageBundle; use Magento\Framework\Locale\Bundle\RegionBundle; +use Magento\Framework\Validator\Locale; /** * Translated lists. @@ -207,7 +208,11 @@ public function getCountryTranslation($value, $locale = null) $locale = $this->localeResolver->getLocale(); } + $language = \Locale::getPrimaryLanguage($locale); $translation = (new RegionBundle())->get($locale)['Countries'][$value]; + if ($value == 'TW' && $language == 'en') { + $translation .= ", Province of China"; + } return $translation ? (string)__($translation) : $translation; } From eafb431b230166b3b7091ba10c47061c34ee8ca7 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Wed, 11 Sep 2019 13:37:03 +0300 Subject: [PATCH 746/841] MC-12353: Frontend area session must not affect admin area --- ...dAreaSessionMustNotAffectAdminAreaTest.xml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml diff --git a/app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml b/app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml new file mode 100644 index 0000000000000..375211e5f2f51 --- /dev/null +++ b/app/code/Magento/PageCache/Test/Mftf/Test/AdminFrontendAreaSessionMustNotAffectAdminAreaTest.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminFrontendAreaSessionMustNotAffectAdminAreaTest"> + <annotations> + <stories value="Backend"/> + <features value="Session cookies"/> + <title value="Frontend area session must not affect admin area"/> + <description value="Frontend area session must not affect admin area"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-12353"/> + <group value="backend"/> + <group value="pagecache"/> + <group value="cookie"/> + </annotations> + <before> + <!-- Create Data --> + <createData entity="_defaultCategory" stepKey="createCategoryA"/> + <createData entity="SubCategoryWithParent" stepKey="createCategoryB"> + <requiredEntity createDataKey="createCategoryA"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createCategoryC"> + <requiredEntity createDataKey="createCategoryB"/> + </createData> + <createData entity="ApiSimpleProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategoryC"/> + </createData> + <createData entity="ApiSimpleProduct" stepKey="createProduct2"> + <requiredEntity createDataKey="createCategoryC"/> + </createData> + <createData entity="ApiSimpleProduct" stepKey="createProduct3"> + <requiredEntity createDataKey="createCategoryA"/> + </createData> + + <magentoCLI command="cache:clean" arguments="full_page" stepKey="clearCache"/> + <actionGroup ref="logout" stepKey="logout"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + <resetCookie userInput="PHPSESSID" stepKey="resetSessionCookie"/> + </before> + <after> + <!-- Delete data --> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createProduct3" stepKey="deleteProduct3"/> + + <deleteData createDataKey="createCategoryC" stepKey="deleteCategoryC"/> + <deleteData createDataKey="createCategoryB" stepKey="deleteCategoryB"/> + <deleteData createDataKey="createCategoryA" stepKey="deleteCategoryA"/> + + <actionGroup ref="logout" stepKey="logoutAdmin"/> + </after> + + <!-- 1. Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + + <!-- 2. Navigate Go to "Catalog"->"Products" --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="onCatalogProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + + <!-- 3. Open separate tab with Storefront --> + <openNewTab stepKey="openNewTab"/> + + <!-- 4. Navigate to Men -> "Tops" -> "Jackets" --> + <amOnPage + url="{{StorefrontCategoryPage.url($$createCategoryA.custom_attributes[url_key]$$/$$createCategoryB.custom_attributes[url_key]$$/$$createCategoryC.custom_attributes[url_key]$$)}}" + stepKey="openCategoryPage"/> + <waitForPageLoad time="60" stepKey="waitForCategoryPage"/> + + <!-- 5. Open admin tab with page with products. Reload this page twice. --> + <switchToPreviousTab stepKey="switchToPreviousTab"/> + <reloadPage stepKey="reloadAdminCatalogPageFirst"/> + <waitForPageLoad stepKey="waitForReloadFirst"/> + <reloadPage stepKey="reloadAdminCatalogPageSecond"/> + <waitForPageLoad stepKey="waitForReloadSecond"/> + + <seeInTitle userInput="Products / Inventory / Catalog / Magento Admin" stepKey="seeAdminProductsPageTitle"/> + <see userInput="Products" selector="{{AdminHeaderSection.pageTitle}}" stepKey="seeAdminProductsPageHeader"/> + + <switchToNextTab stepKey="switchToFrontendTab"/> + <closeTab stepKey="closeFrontendTab"/> + </test> +</tests> From c771fb976b8ce7480723e6555e529bb9394c4428 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Wed, 11 Sep 2019 14:04:20 +0300 Subject: [PATCH 747/841] MC-19701: Country code for Taiwan (Province of China) --- lib/internal/Magento/Framework/Locale/TranslatedLists.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Locale/TranslatedLists.php b/lib/internal/Magento/Framework/Locale/TranslatedLists.php index 1724198f15b9f..5ed552d8219ae 100644 --- a/lib/internal/Magento/Framework/Locale/TranslatedLists.php +++ b/lib/internal/Magento/Framework/Locale/TranslatedLists.php @@ -10,7 +10,6 @@ use Magento\Framework\Locale\Bundle\DataBundle; use Magento\Framework\Locale\Bundle\LanguageBundle; use Magento\Framework\Locale\Bundle\RegionBundle; -use Magento\Framework\Validator\Locale; /** * Translated lists. From 24ae0a99a302105bb913fed3b0d8b52a23cae780 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Wed, 11 Sep 2019 14:52:50 +0300 Subject: [PATCH 748/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- ...sertStorefrontShoppingCartSummaryWithShippingActionGroup.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml index 0ba739e415fb7..fe5887bbf6f7c 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontShoppingCartSummaryWithShippingActionGroup.xml @@ -16,7 +16,7 @@ <argument name="shipping" type="string"/> </arguments> - <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" after="assertSubtotal" stepKey="waitForShippingElementToBeVisible"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.shipping}}" time="60" after="assertSubtotal" stepKey="waitForShippingElementToBeVisible"/> <waitForText userInput="{{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" time="30" after="waitForShippingElementToBeVisible" stepKey="assertShipping"/> </actionGroup> </actionGroups> From fec58c20635bb2abd753dd54d550504b42be0e60 Mon Sep 17 00:00:00 2001 From: Mathew Beane <aepod23@gmail.com> Date: Wed, 11 Sep 2019 10:29:23 -0400 Subject: [PATCH 749/841] Update processor.php Added PHP_EOL to the report output. --- pub/errors/processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pub/errors/processor.php b/pub/errors/processor.php index ab21f791bc021..8b8975efcfdaa 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -480,7 +480,7 @@ protected function _setReportData($reportData) */ public function saveReport($reportData) { - $this->reportData = $reportData; + $this->reportData = $reportData . PHP_EOL; $this->reportId = abs((int)(microtime(true) * random_int(100, 1000))); $this->_reportFile = $this->_reportDir . '/' . $this->reportId; $this->_setReportData($reportData); From cbbdee7447e7f3b01312c2fb4b56b6076df900f4 Mon Sep 17 00:00:00 2001 From: Mathew Beane <aepod23@gmail.com> Date: Wed, 11 Sep 2019 12:37:41 -0400 Subject: [PATCH 750/841] PHP_EOL in proper spot. Moved PHP_EOL to file_put_contents so it is not serialized --- pub/errors/processor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pub/errors/processor.php b/pub/errors/processor.php index 8b8975efcfdaa..d3457fa94b07d 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -480,7 +480,7 @@ protected function _setReportData($reportData) */ public function saveReport($reportData) { - $this->reportData = $reportData . PHP_EOL; + $this->reportData = $reportData; $this->reportId = abs((int)(microtime(true) * random_int(100, 1000))); $this->_reportFile = $this->_reportDir . '/' . $this->reportId; $this->_setReportData($reportData); @@ -489,7 +489,7 @@ public function saveReport($reportData) @mkdir($this->_reportDir, 0777, true); } - @file_put_contents($this->_reportFile, $this->serializer->serialize($reportData)); + @file_put_contents($this->_reportFile, $this->serializer->serialize($reportData). PHP_EOL); if (isset($reportData['skin']) && self::DEFAULT_SKIN != $reportData['skin']) { $this->_setSkin($reportData['skin']); From 1b331351bf97d2b26bb0f9e446475b939a0b2086 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Wed, 11 Sep 2019 12:28:39 -0500 Subject: [PATCH 751/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - updated fixtures and test for sorting --- .../GraphQl/Catalog/ProductSearchTest.php | 23 +++++++++---------- .../_files/products_for_relevance_sorting.php | 12 +++++----- ...roducts_for_relevance_sorting_rollback.php | 2 +- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index bd44a4678de53..58890ee2ea806 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -218,7 +218,7 @@ public function testFilterProductsByDropDownCustomAttribute() products(filter:{ $attributeCode: {eq: "{$optionValue}"} } - sort:{relevance:DESC} + pageSize: 3 currentPage: 1 ) @@ -1493,12 +1493,12 @@ public function testSearchAndSortByRelevance() } QUERY; $response = $this->graphQlQuery($query); - $this->assertEquals(4, $response['products']['total_count']); + $this->assertEquals(3, $response['products']['total_count']); $this->assertNotEmpty($response['products']['filters'], 'Filters should have the Category layer'); $this->assertEquals('Colorful Category', $response['products']['filters'][0]['filter_items'][0]['label']); - $productsInResponse = ['ocean blue Shoes','Blue briefs','Navy Striped Shoes','Grey shorts']; - // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall - for ($i = 0; $i < count($response['products']['items']); $i++) { + $productsInResponse = ['Blue briefs','Navy Striped Shoes','Grey shorts']; + $count = count($response['products']['items']); + for ($i = 0; $i < $count; $i++) { $this->assertEquals($productsInResponse[$i], $response['products']['items'][$i]['name']); } $this->assertCount(2, $response['products']['aggregations']); @@ -1634,11 +1634,10 @@ public function testProductBasicFullTextSearchQuery() $prod1 = $productRepository->get('blue_briefs'); $prod2 = $productRepository->get('grey_shorts'); $prod3 = $productRepository->get('navy-striped-shoes'); - $prod4 = $productRepository->get('ocean-blue-shoes'); $response = $this->graphQlQuery($query); - $this->assertEquals(4, $response['products']['total_count']); + $this->assertEquals(3, $response['products']['total_count']); - $filteredProducts = [$prod1, $prod2, $prod3, $prod4]; + $filteredProducts = [$prod1, $prod2, $prod3]; $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); foreach ($productItemsInResponse as $itemIndex => $itemArray) { $this->assertNotEmpty($itemArray); @@ -1753,8 +1752,8 @@ public function testFilterWithinASpecificPriceRangeSortedByPriceDESC() //verify that by default Price and category are the only layers available $filterNames = ['Category', 'Price']; $this->assertCount(2, $response['products']['filters'], 'Filter count does not match'); - // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall - for ($i = 0; $i < count($response['products']['filters']); $i++) { + $productCount = count($response['products']['filters']); + for ($i = 0; $i < $productCount; $i++) { $this->assertEquals($filterNames[$i], $response['products']['filters'][$i]['name']); } } @@ -2031,8 +2030,8 @@ public function testInvalidPageSize() private function assertProductItems(array $filteredProducts, array $actualResponse) { $productItemsInResponse = array_map(null, $actualResponse['products']['items'], $filteredProducts); - // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall - for ($itemIndex = 0; $itemIndex < count($filteredProducts); $itemIndex++) { + $count = count($filteredProducts); + for ($itemIndex = 0; $itemIndex < $count; $itemIndex++) { $this->assertNotEmpty($productItemsInResponse[$itemIndex]); $this->assertResponseFields( $productItemsInResponse[$itemIndex][0], diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php index 56f7d97e8c0fc..eb1a42635d37b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php @@ -81,13 +81,13 @@ ->setAttributeSetId($defaultAttributeSet) ->setStoreId(1) ->setWebsiteIds([1]) - ->setName('ocean blue Shoes') - ->setSku('ocean-blue-shoes') + ->setName('light green Shoes') + ->setSku('light-green-shoes') ->setPrice(40) ->setWeight(8) - ->setDescription('light blue shoes <b>one</b>') - ->setMetaTitle('light blue shoes meta title') - ->setMetaKeyword('light, blue , women, kids') + ->setDescription('green polka dots shoes <b>one</b>') + ->setMetaTitle('light green shoes meta title') + ->setMetaKeyword('light, green , women, kids') ->setMetaDescription('shoes women kids meta description') ->setStockData(['use_config_manage_stock' => 0]) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) @@ -100,7 +100,7 @@ $productRepository->save($greyProduct); $skus = ['green_socks', 'white_shorts','red_trousers','blue_briefs','grey_shorts', - 'navy-striped-shoes', 'ocean-blue-shoes']; + 'navy-striped-shoes', 'light-green-shoes']; /** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ $categoryLinkManagement = $objectManager->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php index 90da9baf2d60e..888687ec80d6c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php @@ -21,7 +21,7 @@ /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ $productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); $productsToDelete = ['green_socks', 'white_shorts','red_trousers','blue_briefs', - 'grey_shorts', 'navy-striped-shoes','ocean-blue-shoes']; + 'grey_shorts', 'navy-striped-shoes','light-green-shoes']; foreach ($productsToDelete as $sku) { try { From 0d43912118850097deb864eb0786ffda2d1556fc Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Wed, 11 Sep 2019 14:07:22 -0500 Subject: [PATCH 752/841] MC-20108: dd/mm mm/yy Sales order grid Date Filters not working in en_GB locale --- .../Magento/Ui/Component/Form/Element/DataType/Date.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php index 470767af6d319..31d2fe786cfd8 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Date.php @@ -111,14 +111,7 @@ public function getComponentName() public function convertDate($date, $hour = 0, $minute = 0, $second = 0, $setUtcTimeZone = true) { try { - $dateObj = $this->localeDate->date( - new \DateTime( - $date, - new \DateTimeZone($this->localeDate->getConfigTimezone()) - ), - $this->getLocale(), - true - ); + $dateObj = $this->localeDate->date($date, $this->getLocale(), true); $dateObj->setTime($hour, $minute, $second); //convert store date to default date in UTC timezone without DST if ($setUtcTimeZone) { From 7bfbc23565e43b1374d4dd6243345d4fa0267067 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Wed, 11 Sep 2019 14:09:56 -0500 Subject: [PATCH 753/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - address review comments --- .../Model/Config/FilterAttributeReader.php | 12 ++++++++++-- .../Resolver/Products/DataProvider/ProductSearch.php | 2 +- app/code/Magento/CatalogGraphQl/etc/schema.graphqls | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php index 3238a1b6564ad..4f3a88cc788df 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php +++ b/app/code/Magento/CatalogGraphQl/Model/Config/FilterAttributeReader.php @@ -45,16 +45,24 @@ class FilterAttributeReader implements ReaderInterface */ private $collectionFactory; + /** + * @var array + */ + private $exactMatchAttributes = ['sku']; + /** * @param MapperInterface $mapper * @param CollectionFactory $collectionFactory + * @param array $exactMatchAttributes */ public function __construct( MapperInterface $mapper, - CollectionFactory $collectionFactory + CollectionFactory $collectionFactory, + array $exactMatchAttributes = [] ) { $this->mapper = $mapper; $this->collectionFactory = $collectionFactory; + $this->exactMatchAttributes = array_merge($this->exactMatchAttributes, $exactMatchAttributes); } /** @@ -94,7 +102,7 @@ public function read($scope = null) : array */ private function getFilterType(Attribute $attribute): string { - if ($attribute->getAttributeCode() === 'sku') { + if (in_array($attribute->getAttributeCode(), $this->exactMatchAttributes)) { return self::FILTER_EQUAL_TYPE; } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php index 661c5874614b5..ff845f4796763 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ProductSearch.php @@ -95,7 +95,7 @@ public function getList( $searchResults = $this->searchResultsFactory->create(); $searchResults->setSearchCriteria($searchCriteria); $searchResults->setItems($collection->getItems()); - $searchResults->setTotalCount($collection->getSize()); + $searchResults->setTotalCount($searchResult->getTotalCount()); return $searchResults; } diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 781fd4ca88beb..76a58857cebc2 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -338,7 +338,7 @@ type ProductMediaGalleryEntriesVideoContent @doc(description: "ProductMediaGalle video_metadata: String @doc(description: "Optional data about the video.") } -input ProductSortInput @deprecated(reason: "The attributes used in this input are hard-coded, and some of them are not sortable. Use @ProductAttributeSortInput instead") @doc(description: "ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") { +input ProductSortInput @doc(description: "ProductSortInput is deprecated, use @ProductAttributeSortInput instead. ProductSortInput specifies the attribute to use for sorting search results and indicates whether the results are sorted in ascending or descending order.") { name: SortEnum @doc(description: "The product name. Customers use this name to identify the product.") sku: SortEnum @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.") description: SortEnum @doc(description: "Detailed information about the product. The value can include simple HTML tags.") From 2a4a2228de30da83ca6e9bac410ebc37b677086a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torben=20Ho=CC=88hn?= <torhoehn@gmail.com> Date: Wed, 11 Sep 2019 21:46:43 +0200 Subject: [PATCH 754/841] add view model --- .../Block/Product/Renderer/Configurable.php | 18 ------- .../Product/Renderer/Configurable.php | 50 +++++++++++++++++++ .../frontend/layout/catalog_category_view.xml | 13 ++++- ...catalog_product_view_type_configurable.xml | 11 +++- .../layout/catalog_widget_product_list.xml | 15 ++++-- .../layout/catalogsearch_advanced_result.xml | 13 ++++- .../layout/catalogsearch_result_index.xml | 13 ++++- ...list_index_configure_type_configurable.xml | 11 +++- .../templates/product/listing/renderer.phtml | 4 +- .../templates/product/view/renderer.phtml | 8 ++- 10 files changed, 122 insertions(+), 34 deletions(-) create mode 100644 app/code/Magento/Swatches/ViewModel/Product/Renderer/Configurable.php diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php index 19d9340c4049c..a2cae7f7b5a20 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Configurable.php @@ -63,11 +63,6 @@ class Configurable extends \Magento\ConfigurableProduct\Block\Product\View\Type\ */ private const XML_PATH_SWATCHES_PER_PRODUCT = 'catalog/frontend/swatches_per_product'; - /** - * Config path if swatch tooltips are enabled - */ - private const XML_PATH_SHOW_SWATCH_TOOLTIP = 'catalog/frontend/show_swatch_tooltip'; - /** * @var Product */ @@ -216,19 +211,6 @@ public function getNumberSwatchesPerProduct() ); } - /** - * Get config if swatch tooltips should be rendered. - * - * @return string - */ - public function getShowSwatchTooltip() - { - return $this->_scopeConfig->getValue( - self::XML_PATH_SHOW_SWATCH_TOOLTIP, - ScopeInterface::SCOPE_STORE - ); - } - /** * Set product to block * diff --git a/app/code/Magento/Swatches/ViewModel/Product/Renderer/Configurable.php b/app/code/Magento/Swatches/ViewModel/Product/Renderer/Configurable.php new file mode 100644 index 0000000000000..849d79cc58d92 --- /dev/null +++ b/app/code/Magento/Swatches/ViewModel/Product/Renderer/Configurable.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); +namespace Magento\Swatches\ViewModel\Product\Renderer; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * Class Configurable + */ +class Configurable implements ArgumentInterface +{ + /** + * Config path if swatch tooltips are enabled + */ + private const XML_PATH_SHOW_SWATCH_TOOLTIP = 'catalog/frontend/show_swatch_tooltip'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * Configurable constructor. + * + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct(ScopeConfigInterface $scopeConfig) + { + $this->scopeConfig = $scopeConfig; + } + + /** + * Get config if swatch tooltips should be rendered. + * + * @return string + */ + public function getShowSwatchTooltip() + { + return $this->scopeConfig->getValue( + self::XML_PATH_SHOW_SWATCH_TOOLTIP, + ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/app/code/Magento/Swatches/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Swatches/view/frontend/layout/catalog_category_view.xml index c2dc36e83950c..86031a189d798 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/catalog_category_view.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/catalog_category_view.xml @@ -5,10 +5,19 @@ * See COPYING.txt for license details. */ --> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="category.product.type.details.renderers"> - <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" name="category.product.type.details.renderers.configurable" as="configurable" template="Magento_Swatches::product/listing/renderer.phtml" ifconfig="catalog/frontend/show_swatches_in_product_list" /> + <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" + name="category.product.type.details.renderers.configurable" as="configurable" + template="Magento_Swatches::product/listing/renderer.phtml" + ifconfig="catalog/frontend/show_swatches_in_product_list"> + <arguments> + <argument name="configurable_view_model" + xsi:type="object">Magento\Swatches\ViewModel\Product\Renderer\Configurable</argument> + </arguments> + </block> </referenceBlock> </body> </page> diff --git a/app/code/Magento/Swatches/view/frontend/layout/catalog_product_view_type_configurable.xml b/app/code/Magento/Swatches/view/frontend/layout/catalog_product_view_type_configurable.xml index 6188f3957a11d..98346d6ae7e67 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/catalog_product_view_type_configurable.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/catalog_product_view_type_configurable.xml @@ -5,11 +5,18 @@ * See COPYING.txt for license details. */ --> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceContainer name="product.info.options.configurable" remove="true"/> <referenceBlock name="product.info.options.wrapper"> - <block class="Magento\Swatches\Block\Product\Renderer\Configurable" name="product.info.options.swatches" as="swatch_options" before="-" /> + <block class="Magento\Swatches\Block\Product\Renderer\Configurable" name="product.info.options.swatches" + as="swatch_options" before="-"> + <arguments> + <argument name="configurable_view_model" + xsi:type="object">Magento\Swatches\ViewModel\Product\Renderer\Configurable</argument> + </arguments> + </block> </referenceBlock> </body> </page> diff --git a/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml b/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml index 91798cbd9947f..ce31f588c6c8c 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml @@ -3,10 +3,19 @@ ~ See COPYING.txt for license details. --> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="category.product.type.widget.details.renderers"> - <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" name="category.product.type.details.renderers.configurable" as="configurable" template="Magento_Swatches::product/listing/renderer.phtml" ifconfig="catalog/frontend/show_swatches_in_product_list"/> + <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" + name="category.product.type.details.renderers.configurable" as="configurable" + template="Magento_Swatches::product/listing/renderer.phtml" + ifconfig="catalog/frontend/show_swatches_in_product_list"> + <arguments> + <argument name="configurable_view_model" + xsi:type="object">Magento\Swatches\ViewModel\Product\Renderer\Configurable</argument> + </arguments> + </block> </referenceBlock> </body> -</page> \ No newline at end of file +</page> diff --git a/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_advanced_result.xml b/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_advanced_result.xml index c2dc36e83950c..86031a189d798 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_advanced_result.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_advanced_result.xml @@ -5,10 +5,19 @@ * See COPYING.txt for license details. */ --> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="category.product.type.details.renderers"> - <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" name="category.product.type.details.renderers.configurable" as="configurable" template="Magento_Swatches::product/listing/renderer.phtml" ifconfig="catalog/frontend/show_swatches_in_product_list" /> + <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" + name="category.product.type.details.renderers.configurable" as="configurable" + template="Magento_Swatches::product/listing/renderer.phtml" + ifconfig="catalog/frontend/show_swatches_in_product_list"> + <arguments> + <argument name="configurable_view_model" + xsi:type="object">Magento\Swatches\ViewModel\Product\Renderer\Configurable</argument> + </arguments> + </block> </referenceBlock> </body> </page> diff --git a/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_result_index.xml b/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_result_index.xml index 9285d34efcd4c..86031a189d798 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_result_index.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/catalogsearch_result_index.xml @@ -5,10 +5,19 @@ * See COPYING.txt for license details. */ --> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="category.product.type.details.renderers"> - <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" name="category.product.type.details.renderers.configurable" as="configurable" template="Magento_Swatches::product/listing/renderer.phtml" ifconfig="catalog/frontend/show_swatches_in_product_list"/> + <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" + name="category.product.type.details.renderers.configurable" as="configurable" + template="Magento_Swatches::product/listing/renderer.phtml" + ifconfig="catalog/frontend/show_swatches_in_product_list"> + <arguments> + <argument name="configurable_view_model" + xsi:type="object">Magento\Swatches\ViewModel\Product\Renderer\Configurable</argument> + </arguments> + </block> </referenceBlock> </body> </page> diff --git a/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml b/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml index 9982eb98d84da..c8159f1a43fe3 100644 --- a/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml +++ b/app/code/Magento/Swatches/view/frontend/layout/wishlist_index_configure_type_configurable.xml @@ -5,11 +5,18 @@ * See COPYING.txt for license details. */ --> -<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> +<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="product.info.options.configurable" remove="true"/> <referenceBlock name="product.info.options.wrapper"> - <block class="Magento\Swatches\Block\Product\Renderer\Configurable" name="product.info.options.swatches" as="swatch_options" before="-" /> + <block class="Magento\Swatches\Block\Product\Renderer\Configurable" name="product.info.options.swatches" + as="swatch_options" before="-"> + <arguments> + <argument name="configurable_view_model" + xsi:type="object">Magento\Swatches\ViewModel\Product\Renderer\Configurable</argument> + </arguments> + </block> </referenceBlock> </body> </page> diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml index 3c2f15dea0ab4..5838ba9625c6a 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml @@ -7,6 +7,8 @@ <?php /** @var $block \Magento\Swatches\Block\Product\Renderer\Listing\Configurable */ $productId = $block->getProduct()->getId(); +/** @var \Magento\Swatches\ViewModel\Product\Renderer\Configurable $configurableViewModel */ +$configurableViewModel = $block->getConfigurableViewModel() ?> <div class="swatch-opt-<?= $block->escapeHtmlAttr($productId) ?>" data-role="swatch-option-<?= $block->escapeHtmlAttr($productId) ?>"></div> @@ -23,7 +25,7 @@ $productId = $block->getProduct()->getId(); "jsonSwatchConfig": <?= /* @noEscape */ $block->getJsonSwatchConfig() ?>, "mediaCallback": "<?= $block->escapeJs($block->escapeUrl($block->getMediaCallback())) ?>", "jsonSwatchImageSizeConfig": <?= /* @noEscape */ $block->getJsonSwatchSizeConfig() ?>, - "showTooltip": <?= $block->escapeJs($block->getShowSwatchTooltip()) ?> + "showTooltip": <?= $block->escapeJs($configurableViewModel->getShowSwatchTooltip()) ?> } } } diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml index 46666e880bb03..bfabd5f3ab38f 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/view/renderer.phtml @@ -4,7 +4,11 @@ * See COPYING.txt for license details. */ ?> -<?php /** @var $block \Magento\Swatches\Block\Product\Renderer\Configurable */ ?> +<?php +/** @var $block \Magento\Swatches\Block\Product\Renderer\Configurable */ +/** @var \Magento\Swatches\ViewModel\Product\Renderer\Configurable $configurableViewModel */ +$configurableViewModel = $block->getConfigurableViewModel() +?> <div class="swatch-opt" data-role="swatch-options"></div> <script type="text/x-magento-init"> @@ -16,7 +20,7 @@ "mediaCallback": "<?= $block->escapeJs($block->escapeUrl($block->getMediaCallback())) ?>", "gallerySwitchStrategy": "<?= $block->escapeJs($block->getVar('gallery_switch_strategy', 'Magento_ConfigurableProduct')) ?: 'replace'; ?>", "jsonSwatchImageSizeConfig": <?= /* @noEscape */ $block->getJsonSwatchSizeConfig() ?>, - "showTooltip": <?= $block->escapeJs($block->getShowSwatchTooltip()) ?> + "showTooltip": <?= $block->escapeJs($configurableViewModel->getShowSwatchTooltip()) ?> } }, "*" : { From 1c161eb92c8a08bc177c80208463e35ece08ea94 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Wed, 11 Sep 2019 16:46:56 -0500 Subject: [PATCH 755/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - removing sort by relevance from search to test in jenkins --- .../testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 58890ee2ea806..f3dbff8be9d24 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -1451,7 +1451,6 @@ public function testSearchAndSortByRelevance() products( search:"{$search_term}" - sort:{relevance:DESC} pageSize: 5 currentPage: 1 ) From ddeb980c2c9aec565ad1b06db482a6e8b518b2eb Mon Sep 17 00:00:00 2001 From: Buba Suma <soumah@adobe.com> Date: Wed, 11 Sep 2019 13:43:03 -0500 Subject: [PATCH 756/841] MC-19906: ElasticSearch doesn't match results MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix the matching query text is used for search instead of the original one. Because of inaccurate string comparison of utf8_general_ci, the search_query result text may be different from the original text (e.g organos, Organos, Órganos) --- .../Magento/Search/Model/QueryFactory.php | 16 ++-- .../Test/Unit/Model/QueryFactoryTest.php | 81 ++++++++++++------- 2 files changed, 63 insertions(+), 34 deletions(-) diff --git a/app/code/Magento/Search/Model/QueryFactory.php b/app/code/Magento/Search/Model/QueryFactory.php index 2122451742402..4186b5c3055f4 100644 --- a/app/code/Magento/Search/Model/QueryFactory.php +++ b/app/code/Magento/Search/Model/QueryFactory.php @@ -5,13 +5,15 @@ */ namespace Magento\Search\Model; -use Magento\Search\Helper\Data; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Helper\Context; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Stdlib\StringUtils as StdlibString; +use Magento\Search\Helper\Data; /** + * Search Query Factory + * * @api * @since 100.0.2 */ @@ -72,7 +74,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function get() { @@ -82,9 +84,7 @@ public function get() $rawQueryText = $this->getRawQueryText(); $preparedQueryText = $this->getPreparedQueryText($rawQueryText, $maxQueryLength); $query = $this->create()->loadByQueryText($preparedQueryText); - if (!$query->getId()) { - $query->setQueryText($preparedQueryText); - } + $query->setQueryText($preparedQueryText); $query->setIsQueryTextExceeded($this->isQueryTooLong($rawQueryText, $maxQueryLength)); $query->setIsQueryTextShort($this->isQueryTooShort($rawQueryText, $minQueryLength)); $this->query = $query; @@ -117,6 +117,8 @@ private function getRawQueryText() } /** + * Prepare query text + * * @param string $queryText * @param int|string $maxQueryLength * @return string @@ -130,6 +132,8 @@ private function getPreparedQueryText($queryText, $maxQueryLength) } /** + * Check if the provided text exceeds the provided length + * * @param string $queryText * @param int|string $maxQueryLength * @return bool @@ -140,6 +144,8 @@ private function isQueryTooLong($queryText, $maxQueryLength) } /** + * Check if the provided text is shorter than the provided length + * * @param string $queryText * @param int|string $minQueryLength * @return bool diff --git a/app/code/Magento/Search/Test/Unit/Model/QueryFactoryTest.php b/app/code/Magento/Search/Test/Unit/Model/QueryFactoryTest.php index 3df457b0d4497..f66c1c7dd9e3f 100644 --- a/app/code/Magento/Search/Test/Unit/Model/QueryFactoryTest.php +++ b/app/code/Magento/Search/Test/Unit/Model/QueryFactoryTest.php @@ -5,14 +5,14 @@ */ namespace Magento\Search\Test\Unit\Model; -use Magento\Search\Helper\Data; use Magento\Framework\App\Helper\Context; use Magento\Framework\App\RequestInterface; use Magento\Framework\ObjectManagerInterface; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Search\Model\QueryFactory; use Magento\Framework\Stdlib\StringUtils; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Search\Helper\Data; use Magento\Search\Model\Query; +use Magento\Search\Model\QueryFactory; /** * Class QueryFactoryTest tests Magento\Search\Model\QueryFactory @@ -67,7 +67,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $this->query = $this->getMockBuilder(Query::class) - ->setMethods(['setIsQueryTextExceeded', 'setIsQueryTextShort', 'loadByQueryText', 'getId', 'setQueryText']) + ->setMethods(['setIsQueryTextExceeded', 'setIsQueryTextShort', 'loadByQueryText', 'getId']) ->disableOriginalConstructor() ->getMock(); @@ -124,7 +124,6 @@ public function testGetNewQuery() $isQueryTextExceeded = false; $isQueryTextShort = false; - $this->mockSetQueryTextNeverExecute($cleanedRawText); $this->mockString($cleanedRawText); $this->mockQueryLengths($maxQueryLength, $minQueryLength); $this->mockGetRawQueryText($rawQueryText); @@ -135,6 +134,7 @@ public function testGetNewQuery() $result = $this->model->get(); $this->assertSame($this->query, $result); + $this->assertSearchQuery($cleanedRawText); } /** @@ -150,7 +150,6 @@ public function testGetQueryTwice() $isQueryTextExceeded = false; $isQueryTextShort = false; - $this->mockSetQueryTextNeverExecute($cleanedRawText); $this->mockString($cleanedRawText); $this->mockQueryLengths($maxQueryLength, $minQueryLength); $this->mockGetRawQueryText($rawQueryText); @@ -163,6 +162,7 @@ public function testGetQueryTwice() $result = $this->model->get(); $this->assertSame($this->query, $result, 'After second execution queries are not same'); + $this->assertSearchQuery($cleanedRawText); } /** @@ -184,7 +184,6 @@ public function testGetTooLongQuery() ->withConsecutive([$cleanedRawText, 0, $maxQueryLength]) ->willReturn($subRawText); - $this->mockSetQueryTextNeverExecute($cleanedRawText); $this->mockString($cleanedRawText); $this->mockQueryLengths($maxQueryLength, $minQueryLength); $this->mockGetRawQueryText($rawQueryText); @@ -194,6 +193,7 @@ public function testGetTooLongQuery() $result = $this->model->get(); $this->assertSame($this->query, $result); + $this->assertSearchQuery($subRawText); } /** @@ -209,7 +209,6 @@ public function testGetTooShortQuery() $isQueryTextExceeded = false; $isQueryTextShort = true; - $this->mockSetQueryTextNeverExecute($cleanedRawText); $this->mockString($cleanedRawText); $this->mockQueryLengths($maxQueryLength, $minQueryLength); $this->mockGetRawQueryText($rawQueryText); @@ -219,6 +218,7 @@ public function testGetTooShortQuery() $result = $this->model->get(); $this->assertSame($this->query, $result); + $this->assertSearchQuery($cleanedRawText); } /** @@ -234,7 +234,6 @@ public function testGetQueryWithoutId() $isQueryTextExceeded = false; $isQueryTextShort = false; - $this->mockSetQueryTextOnceExecute($cleanedRawText); $this->mockString($cleanedRawText); $this->mockQueryLengths($maxQueryLength, $minQueryLength); $this->mockGetRawQueryText($rawQueryText); @@ -244,6 +243,35 @@ public function testGetQueryWithoutId() $result = $this->model->get(); $this->assertSame($this->query, $result); + $this->assertSearchQuery($cleanedRawText); + } + + /** + * Test for inaccurate match of search query in query_text table + * + * Because of inaccurate string comparison of utf8_general_ci, + * the search_query result text may be different from the original text (e.g organos, Organos, Órganos) + */ + public function testInaccurateQueryTextMatch() + { + $queryId = 1; + $maxQueryLength = 100; + $minQueryLength = 3; + $rawQueryText = 'Órganos'; + $cleanedRawText = 'Órganos'; + $isQueryTextExceeded = false; + $isQueryTextShort = false; + + $this->mockString($cleanedRawText); + $this->mockQueryLengths($maxQueryLength, $minQueryLength); + $this->mockGetRawQueryText($rawQueryText); + $this->mockSimpleQuery($cleanedRawText, $queryId, $isQueryTextExceeded, $isQueryTextShort, 'Organos'); + + $this->mockCreateQuery(); + + $result = $this->model->get(); + $this->assertSame($this->query, $result); + $this->assertSearchQuery($cleanedRawText); } /** @@ -305,15 +333,25 @@ private function mockCreateQuery() * @param int $queryId * @param bool $isQueryTextExceeded * @param bool $isQueryTextShort + * @param string $matchedQueryText * @return void */ - private function mockSimpleQuery($cleanedRawText, $queryId, $isQueryTextExceeded, $isQueryTextShort) - { + private function mockSimpleQuery( + string $cleanedRawText, + ?int $queryId, + bool $isQueryTextExceeded, + bool $isQueryTextShort, + string $matchedQueryText = null + ) { + if (null === $matchedQueryText) { + $matchedQueryText = $cleanedRawText; + } $this->query->expects($this->once()) ->method('loadByQueryText') ->withConsecutive([$cleanedRawText]) ->willReturnSelf(); - $this->query->expects($this->once()) + $this->query->setData(['query_text' => $matchedQueryText]); + $this->query->expects($this->any()) ->method('getId') ->willReturn($queryId); $this->query->expects($this->once()) @@ -328,23 +366,8 @@ private function mockSimpleQuery($cleanedRawText, $queryId, $isQueryTextExceeded * @param string $cleanedRawText * @return void */ - private function mockSetQueryTextNeverExecute($cleanedRawText) + private function assertSearchQuery($cleanedRawText) { - $this->query->expects($this->never()) - ->method('setQueryText') - ->withConsecutive([$cleanedRawText]) - ->willReturnSelf(); - } - - /** - * @param string $cleanedRawText - * @return void - */ - private function mockSetQueryTextOnceExecute($cleanedRawText) - { - $this->query->expects($this->once()) - ->method('setQueryText') - ->withConsecutive([$cleanedRawText]) - ->willReturnSelf(); + $this->assertEquals($cleanedRawText, $this->query->getQueryText()); } } From db8b074a4a8499915969640cdd0e3cbdc6ad426b Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Thu, 12 Sep 2019 09:51:20 +0300 Subject: [PATCH 757/841] MC-6443: Product custom URL Key is preserved when assigned to a Category (without custom URL Key) alongside with another Product without custom URL Key --- .../Mftf/ActionGroup/AdminCategoryActionGroup.xml | 8 ++++---- ...edWhenAssignedToCategoryWithoutCustomURLKey.xml | 14 ++++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index f2005219ffbc0..96575ee475a27 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -399,19 +399,19 @@ <actionGroup name="AdminCategoryAssignProduct"> <annotations> - <description>Assign products to category - using "Products in Category" tab.</description> + <description>Requires navigation to category creation/edit page. Assign products to category - using "Products in Category" tab.</description> </annotations> <arguments> <argument name="productSku" type="string"/> </arguments> <conditionalClick selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" dependentSelector="{{CatalogProductsSection.resetFilter}}" visible="false" stepKey="clickOnProductInCategory"/> - <waitForPageLoad stepKey="waitForProductsInCategoryOpened"/> + <waitForPageLoad time="30" stepKey="waitForProductsInCategoryOpened"/> <click selector="{{CatalogProductsSection.resetFilter}}" stepKey="clickOnResetFilter"/> - <waitForPageLoad stepKey="waitForProductsToLoad"/> + <waitForPageLoad time="30" stepKey="waitForProductsToLoad"/> <fillField selector="{{AdminCategoryContentSection.productTableColumnSku}}" userInput="{{productSku}}" stepKey="selectProduct"/> <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> - <waitForPageLoad stepKey="waitForSearch"/> + <waitForPageLoad time="30" stepKey="waitForSearch"/> <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml index d7a01ac0c8612..bae81513de632 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey.xml @@ -11,10 +11,12 @@ <test name="AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKey"> <annotations> <stories value="Product"/> + <features value="Catalog"/> <title value="Product custom URL Key is preserved when assigned to a Category (without custom URL Key) alongside with another Product without custom URL Key"/> <description value="The test verifies that product custom URL Key is preserved when assigned to a Category (without custom URL Key) alongside with another Product without custom URL Key."/> <severity value="MAJOR"/> <testCaseId value="MC-6443"/> + <useCaseId value="MAGETWO-90331"/> <group value="catalog"/> </annotations> <before> @@ -74,9 +76,9 @@ <actionGroup ref="saveCategoryForm" stepKey="saveCategory"/> - <executeJS function="var s = '$createCategory.name$'; var res=s.toLowerCase(); return res;" stepKey="categoryNameLower" /> - <executeJS function="var s = '$createSimpleProductFirst.name$'; var res=s.toLowerCase(); return res;" stepKey="simpleProductFirstNameLower" /> - <executeJS function="var s = '$createSimpleProductSecond.name$';var res=s.toLowerCase(); return res;" stepKey="simpleProductSecondNameLower" /> + <executeJS function="return '$createCategory.name$'.toLowerCase();" stepKey="categoryNameLower" /> + <executeJS function="return '$createSimpleProductFirst.name$'.toLowerCase();" stepKey="simpleProductFirstNameLower" /> + <executeJS function="return '$createSimpleProductSecond.name$'.toLowerCase();" stepKey="simpleProductSecondNameLower" /> <!-- Make assertions on frontend --> <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefrontPage"/> @@ -85,11 +87,13 @@ <!-- Open first product --> <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($createSimpleProductFirst.name$)}}" stepKey="openFirstProduct"/> + <waitForPageLoad time="30" stepKey="waitForFirstProduct"/> <seeInCurrentUrl url="{$simpleProductFirstNameLower}.html" stepKey="checkFirstSimpleProductUrlKey"/> - <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryView"/> + <amOnPage url="{{StorefrontCategoryPage.url($createCategory.custom_attributes[url_key]$)}}" stepKey="onCategoryView"/> <!-- Open second product --> <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($createSimpleProductSecond.name$)}}" stepKey="openSecondProduct"/> + <waitForPageLoad time="30" stepKey="waitForSecondProduct"/> <seeInCurrentUrl url="{$simpleProductSecondNameLower}.html" stepKey="checkSecondSimpleProductUrlKey"/> <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchToCustomStoreView"> @@ -101,11 +105,13 @@ <!-- Open product first --> <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSimpleProductFirst.name$$)}}" stepKey="openFirstSimpleProduct"/> + <waitForPageLoad time="30" stepKey="waitForFirstSimpleProduct"/> <seeInCurrentUrl url="{$simpleProductFirstNameLower}.html" stepKey="assertFirstSimpleProductUrlKey"/> <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($createCategory.name$)}}" stepKey="openCategoryView"/> <!-- Open product2 --> <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($createSimpleProductSecond.name$)}}" stepKey="openSecondSimpleProduct"/> + <waitForPageLoad time="30" stepKey="waitForSecondSimpleProduct"/> <seeInCurrentUrl url="u2.html" stepKey="assertSecondSimpleProductUrlKey"/> </test> </tests> From 03ed173cf3e8784d33c42484ac5bbfbcfaae68d8 Mon Sep 17 00:00:00 2001 From: Serhiy Yelahin <serhiy.yelahin@transoftgroup.com> Date: Thu, 12 Sep 2019 10:39:13 +0300 Subject: [PATCH 758/841] MC-19701: Country code for Taiwan (Province of China) --- app/code/Magento/Config/i18n/en_US.csv | 1 + lib/internal/Magento/Framework/Locale/TranslatedLists.php | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/code/Magento/Config/i18n/en_US.csv b/app/code/Magento/Config/i18n/en_US.csv index 9770bf4b94c27..ceb1efdc8b77d 100644 --- a/app/code/Magento/Config/i18n/en_US.csv +++ b/app/code/Magento/Config/i18n/en_US.csv @@ -118,3 +118,4 @@ Dashboard,Dashboard "Web Section","Web Section" "Store Email Addresses Section","Store Email Addresses Section" "Email to a Friend","Email to a Friend" +"Taiwan","Taiwan, Province of China" diff --git a/lib/internal/Magento/Framework/Locale/TranslatedLists.php b/lib/internal/Magento/Framework/Locale/TranslatedLists.php index 5ed552d8219ae..2087564dcec20 100644 --- a/lib/internal/Magento/Framework/Locale/TranslatedLists.php +++ b/lib/internal/Magento/Framework/Locale/TranslatedLists.php @@ -207,11 +207,7 @@ public function getCountryTranslation($value, $locale = null) $locale = $this->localeResolver->getLocale(); } - $language = \Locale::getPrimaryLanguage($locale); $translation = (new RegionBundle())->get($locale)['Countries'][$value]; - if ($value == 'TW' && $language == 'en') { - $translation .= ", Province of China"; - } return $translation ? (string)__($translation) : $translation; } From 6630bad98faff1777c9608a697cdaf016db316ad Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Thu, 12 Sep 2019 12:07:49 +0300 Subject: [PATCH 759/841] MC-3377: Admin should be able to associate grouped product to websites --- ...nAssociateGroupedProductToWebsitesTest.xml | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAssociateGroupedProductToWebsitesTest.xml diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAssociateGroupedProductToWebsitesTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAssociateGroupedProductToWebsitesTest.xml new file mode 100644 index 0000000000000..3827666252478 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAssociateGroupedProductToWebsitesTest.xml @@ -0,0 +1,116 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminAssociateGroupedProductToWebsitesTest"> + <annotations> + <features value="GroupedProduct"/> + <stories value="Create/Edit grouped product in Admin"/> + <title value="Admin should be able to associate grouped product to websites"/> + <description value="Admin should be able to associate grouped product to websites"/> + <testCaseId value="MC-3377"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="groupedProduct"/> + </annotations> + + <before> + <!-- Set Store Code To Urls --> + <magentoCLI command="config:set {{StorefrontEnableAddStoreCodeToUrls.path}} {{StorefrontEnableAddStoreCodeToUrls.value}}" stepKey="setAddStoreCodeToUrlsToYes"/> + + <!-- Create grouped product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + <createData entity="ApiGroupedProduct" stepKey="createGroupedProduct"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create website--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createSecondWebsite"> + <argument name="newWebsiteName" value="{{secondCustomWebsite.name}}"/> + <argument name="websiteCode" value="{{secondCustomWebsite.code}}"/> + </actionGroup> + <!-- Create second store --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStoreGroup"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + </actionGroup> + <!-- Create second store view --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="StoreGroup" value="SecondStoreGroupUnique"/> + <argument name="customStore" value="SecondStoreUnique"/> + </actionGroup> + + <!-- Reindex --> + <magentoCLI command="indexer:reindex" stepKey="reindexAllIndexes"/> + </before> + + <after> + <!-- Disable Store Code To Urls --> + <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" stepKey="setAddStoreCodeToUrlsToNo"/> + + <!-- Delete product data --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> + + <!-- Delete second website --> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{secondCustomWebsite.name}}"/> + </actionGroup> + + <actionGroup ref="NavigateToAndResetProductGridToDefaultView" stepKey="resetProductGridFilter"/> + + <!-- Admin logout --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open product page and assign grouped project to second website --> + <actionGroup ref="filterAndSelectProduct" stepKey="openAdminProductPage"> + <argument name="productSku" value="$$createGroupedProduct.sku$$"/> + </actionGroup> + <actionGroup ref="AdminAssignProductInWebsiteActionGroup" stepKey="assignProductToSecondWebsite"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + </actionGroup> + <actionGroup ref="AdminUnassignProductInWebsiteActionGroup" stepKey="unassignProductFromDefaultWebsite"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveGroupedProduct"/> + + <!-- Assert product is assigned to Second website --> + <actionGroup ref="AssertProductIsAssignedToWebsite" stepKey="seeCustomWebsiteIsChecked"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + </actionGroup> + + <!-- Assert product is not assigned to Main website --> + <actionGroup ref="AssertProductIsNotAssignedToWebsite" stepKey="seeMainWebsiteIsNotChecked"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + </actionGroup> + + <!-- Go to frontend and open product on Main website --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrl" value="$$createGroupedProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!-- Assert 404 page --> + <actionGroup ref="StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup" stepKey="assertPageNotFoundErrorOnProductDetailPage"> + <argument name="product" value="$$createGroupedProduct$$"/> + </actionGroup> + + <!-- Assert grouped product on Second website --> + <actionGroup ref="StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup" stepKey="openProductPageUsingStoreCodeInUrl"> + <argument name="product" value="$$createGroupedProduct$$"/> + <argument name="storeView" value="SecondStoreUnique"/> + </actionGroup> + </test> +</tests> From d2c03a6041ddc6e409ccaee3d6dadc97b8868ada Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Thu, 12 Sep 2019 11:45:40 +0300 Subject: [PATCH 760/841] MC-3344: Admin should be able to associate bundle product to websites --- ...inAssociateBundleProductToWebsitesTest.xml | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml new file mode 100644 index 0000000000000..505a319c5c44f --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAssociateBundleProductToWebsitesTest.xml @@ -0,0 +1,126 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminAssociateBundleProductToWebsitesTest"> + <annotations> + <features value="Bundle"/> + <stories value="Create/Edit bundle product in Admin"/> + <title value="Admin should be able to associate bundle product to websites"/> + <description value="Admin should be able to associate bundle product to websites"/> + <testCaseId value="MC-3344"/> + <severity value="CRITICAL"/> + <group value="bundle"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Configure Store URLs --> + <magentoCLI command="config:set {{StorefrontEnableAddStoreCodeToUrls.path}} {{StorefrontEnableAddStoreCodeToUrls.value}}" stepKey="setAddStoreCodeToUrlsToYes"/> + + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Create Simple product --> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"/> + + <!-- Create Bundle product --> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createNewBundleLink"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + + <!-- Reindex --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create website--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createSecondWebsite"> + <argument name="newWebsiteName" value="{{secondCustomWebsite.name}}"/> + <argument name="websiteCode" value="{{secondCustomWebsite.code}}"/> + </actionGroup> + <!-- Create second store --> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStoreGroup"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + </actionGroup> + <!-- Create second store view --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="StoreGroup" value="SecondStoreGroupUnique"/> + <argument name="customStore" value="SecondStoreUnique"/> + </actionGroup> + </before> + <after> + <!-- Disabled Store URLs --> + <magentoCLI command="config:set {{StorefrontDisableAddStoreCodeToUrls.path}} {{StorefrontDisableAddStoreCodeToUrls.value}}" stepKey="setAddStoreCodeToUrlsToNo"/> + + <!-- Delete simple product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <!-- Delete bundle product --> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + + <!-- Delete second website --> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{secondCustomWebsite.name}}"/> + </actionGroup> + + <actionGroup ref="NavigateToAndResetProductGridToDefaultView" stepKey="resetProductGridFilter"/> + + <!-- Admin logout --> + <actionGroup ref="logout" stepKey="adminLogout"/> + </after> + + <!-- Open product page and assign grouped project to second website --> + <actionGroup ref="filterAndSelectProduct" stepKey="openAdminProductPage"> + <argument name="productSku" value="$$createBundleProduct.sku$$"/> + </actionGroup> + <actionGroup ref="AdminAssignProductInWebsiteActionGroup" stepKey="assignProductToSecondWebsite"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + </actionGroup> + <actionGroup ref="AdminUnassignProductInWebsiteActionGroup" stepKey="unassignProductFromDefaultWebsite"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveGroupedProduct"/> + + <!-- Assert product is assigned to Second website --> + <actionGroup ref="AssertProductIsAssignedToWebsite" stepKey="seeCustomWebsiteIsChecked"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + </actionGroup> + + <!-- Assert product is not assigned to Main website --> + <actionGroup ref="AssertProductIsNotAssignedToWebsite" stepKey="seeMainWebsiteIsNotChecked"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + </actionGroup> + + <!-- Go to frontend and open product on Main website --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrl" value="$$createBundleProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!-- Assert 404 page --> + <actionGroup ref="StorefrontAssertPageNotFoundErrorOnProductDetailPageActionGroup" stepKey="assertPageNotFoundError"> + <argument name="product" value="$$createBundleProduct$$"/> + </actionGroup> + + <!-- Assert product is present at Second website --> + <actionGroup ref="StorefrontOpenProductPageUsingStoreCodeInUrlActionGroup" stepKey="openProductPageUsingStoreCodeInUrl"> + <argument name="product" value="$$createBundleProduct$$"/> + <argument name="storeView" value="SecondStoreUnique"/> + </actionGroup> + </test> +</tests> From 41a35bd439a3ddf68d016d5d699fc32c4d19976d Mon Sep 17 00:00:00 2001 From: Alex Taranovsky <firster@atwix.com> Date: Wed, 28 Aug 2019 16:21:54 +0300 Subject: [PATCH 761/841] magento/magento2#: Captcha. Improvement. Replace deprecated addError with addErrorMessage. magento/magento2#: Replace deprecated Magento\Customer\Model\Customer::isConfirmationRequired method magento/magento2#: Replace deprecated addError, addSuccess, addException methods in Magento/Customer/Controller/Account/Confirmation.php --- .../Observer/CheckContactUsFormObserver.php | 5 ++- .../Observer/CheckForgotpasswordObserver.php | 5 ++- .../Observer/CheckUserCreateObserver.php | 7 +++- .../Observer/CheckUserEditObserver.php | 11 ++++-- ...CheckUserForgotPasswordBackendObserver.php | 7 +++- .../Observer/CheckUserLoginObserver.php | 10 +++-- .../CheckContactUsFormObserverTest.php | 9 +++-- .../CheckForgotpasswordObserverTest.php | 2 +- .../Observer/CheckUserCreateObserverTest.php | 2 +- .../Observer/CheckUserEditObserverTest.php | 2 +- .../Observer/CheckUserLoginObserverTest.php | 2 +- .../Controller/Account/Confirmation.php | 21 +++++++---- app/code/Magento/Customer/Model/Customer.php | 29 ++++++++++----- .../Customer/Model/ResourceModel/Customer.php | 25 +++++++++++-- app/code/Magento/Customer/Model/Session.php | 37 ++++++++++++++++--- .../Customer/Test/Unit/Model/SessionTest.php | 9 ++--- 16 files changed, 132 insertions(+), 51 deletions(-) diff --git a/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php b/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php index 91737c1a3d779..8c1da0e1ef104 100644 --- a/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckContactUsFormObserver.php @@ -9,6 +9,9 @@ use Magento\Framework\App\Request\DataPersistorInterface; use Magento\Framework\App\ObjectManager; +/** + * Class CheckContactUsFormObserver + */ class CheckContactUsFormObserver implements ObserverInterface { /** @@ -76,7 +79,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) /** @var \Magento\Framework\App\Action\Action $controller */ $controller = $observer->getControllerAction(); if (!$captcha->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) { - $this->messageManager->addError(__('Incorrect CAPTCHA.')); + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA.')); $this->getDataPersistor()->set($formId, $controller->getRequest()->getPostValue()); $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); $this->redirect->redirect($controller->getResponse(), 'contact/index/index'); diff --git a/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php b/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php index 0736c7514a568..623d11903926e 100644 --- a/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckForgotpasswordObserver.php @@ -7,6 +7,9 @@ use Magento\Framework\Event\ObserverInterface; +/** + * Class CheckForgotpasswordObserver + */ class CheckForgotpasswordObserver implements ObserverInterface { /** @@ -69,7 +72,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) /** @var \Magento\Framework\App\Action\Action $controller */ $controller = $observer->getControllerAction(); if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) { - $this->messageManager->addError(__('Incorrect CAPTCHA')); + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); $this->redirect->redirect($controller->getResponse(), '*/*/forgotpassword'); } diff --git a/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php b/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php index 6d2ed4d1050ca..ef66116432f55 100644 --- a/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckUserCreateObserver.php @@ -7,6 +7,11 @@ use Magento\Framework\Event\ObserverInterface; +/** + * Class CheckUserCreateObserver + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class CheckUserCreateObserver implements ObserverInterface { /** @@ -86,7 +91,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) /** @var \Magento\Framework\App\Action\Action $controller */ $controller = $observer->getControllerAction(); if (!$captchaModel->isCorrect($this->captchaStringResolver->resolve($controller->getRequest(), $formId))) { - $this->messageManager->addError(__('Incorrect CAPTCHA')); + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); $this->_session->setCustomerFormData($controller->getRequest()->getPostValue()); $url = $this->_urlManager->getUrl('*/*/create', ['_nosecret' => true]); diff --git a/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php b/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php index 9d3cd8d367093..70ccfeae6b7e7 100644 --- a/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php @@ -11,7 +11,9 @@ use Magento\Framework\App\Config\ScopeConfigInterface; /** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * Class CheckUserEditObserver + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class CheckUserEditObserver implements ObserverInterface { @@ -96,7 +98,8 @@ public function __construct( * Check Captcha On Forgot Password Page * * @param \Magento\Framework\Event\Observer $observer - * @return $this + * @return $this|void + * @throws \Magento\Framework\Exception\SessionException */ public function execute(\Magento\Framework\Event\Observer $observer) { @@ -119,9 +122,9 @@ public function execute(\Magento\Framework\Event\Observer $observer) 'The account is locked. Please wait and try again or contact %1.', $this->scopeConfig->getValue('contact/email/recipient_email') ); - $this->messageManager->addError($message); + $this->messageManager->addErrorMessage($message); } - $this->messageManager->addError(__('Incorrect CAPTCHA')); + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); $this->actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); $this->redirect->redirect($controller->getResponse(), '*/*/edit'); } diff --git a/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php b/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php index 2de93dcf6b59b..e11e48a527169 100644 --- a/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckUserForgotPasswordBackendObserver.php @@ -7,6 +7,11 @@ use Magento\Framework\Event\ObserverInterface; +/** + * Class CheckUserForgotPasswordBackendObserver + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class CheckUserForgotPasswordBackendObserver implements ObserverInterface { /** @@ -76,7 +81,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) ) { $this->_session->setEmail((string)$controller->getRequest()->getPost('email')); $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); - $this->messageManager->addError(__('Incorrect CAPTCHA')); + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); $controller->getResponse()->setRedirect( $controller->getUrl('*/*/forgotpassword', ['_nosecret' => true]) ); diff --git a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php index dd4974c5d842c..27507423e77eb 100644 --- a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php @@ -6,10 +6,10 @@ namespace Magento\Captcha\Observer; +use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Model\AuthenticationInterface; use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Customer\Api\CustomerRepositoryInterface; /** * Check captcha on user login page observer. @@ -64,6 +64,8 @@ class CheckUserLoginObserver implements ObserverInterface protected $authentication; /** + * CheckUserLoginObserver constructor. + * * @param \Magento\Captcha\Helper\Data $helper * @param \Magento\Framework\App\ActionFlag $actionFlag * @param \Magento\Framework\Message\ManagerInterface $messageManager @@ -125,8 +127,7 @@ private function getAuthentication() * Check captcha on user login page * * @param \Magento\Framework\Event\Observer $observer - * @throws NoSuchEntityException - * @return $this + * @return $this|void */ public function execute(\Magento\Framework\Event\Observer $observer) { @@ -143,10 +144,11 @@ public function execute(\Magento\Framework\Event\Observer $observer) try { $customer = $this->getCustomerRepository()->get($login); $this->getAuthentication()->processAuthenticationFailure($customer->getId()); + // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock } catch (NoSuchEntityException $e) { //do nothing as customer existence is validated later in authenticate method } - $this->messageManager->addError(__('Incorrect CAPTCHA')); + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); $this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); $this->_session->setUsername($login); $beforeUrl = $this->_session->getBeforeAuthUrl(); diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php index 08f76aa74ac6d..83bfb2910f9f8 100644 --- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckContactUsFormObserverTest.php @@ -69,7 +69,10 @@ protected function setUp() $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\ManagerInterface::class); $this->redirectMock = $this->createMock(\Magento\Framework\App\Response\RedirectInterface::class); $this->captchaStringResolverMock = $this->createMock(\Magento\Captcha\Observer\CaptchaStringResolver::class); - $this->sessionMock = $this->createPartialMock(\Magento\Framework\Session\SessionManager::class, ['addError']); + $this->sessionMock = $this->createPartialMock( + \Magento\Framework\Session\SessionManager::class, + ['addErrorMessage'] + ); $this->dataPersistorMock = $this->getMockBuilder(\Magento\Framework\App\Request\DataPersistorInterface::class) ->getMockForAbstractClass(); @@ -116,7 +119,7 @@ public function testCheckContactUsFormWhenCaptchaIsRequiredAndValid() $this->helperMock->expects($this->any()) ->method('getCaptcha') ->with($formId)->willReturn($this->captchaMock); - $this->sessionMock->expects($this->never())->method('addError'); + $this->sessionMock->expects($this->never())->method('addErrorMessage'); $this->checkContactUsFormObserver->execute( new \Magento\Framework\Event\Observer(['controller_action' => $controller]) @@ -163,7 +166,7 @@ public function testCheckContactUsFormRedirectsCustomerWithWarningMessageWhenCap ->method('getCaptcha') ->with($formId) ->willReturn($this->captchaMock); - $this->messageManagerMock->expects($this->once())->method('addError')->with($warningMessage); + $this->messageManagerMock->expects($this->once())->method('addErrorMessage')->with($warningMessage); $this->actionFlagMock->expects($this->once()) ->method('set') ->with('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true); diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php index b05a3b2e34af0..93b58191cc334 100644 --- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckForgotpasswordObserverTest.php @@ -138,7 +138,7 @@ public function testCheckForgotpasswordRedirects() )->will( $this->returnValue($this->_captcha) ); - $this->_messageManager->expects($this->once())->method('addError')->with($warningMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($warningMessage); $this->_actionFlag->expects( $this->once() )->method( diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php index 8dc67437f4879..a57faabda99eb 100644 --- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserCreateObserverTest.php @@ -151,7 +151,7 @@ public function testCheckUserCreateRedirectsError() )->will( $this->returnValue($this->_captcha) ); - $this->_messageManager->expects($this->once())->method('addError')->with($warningMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($warningMessage); $this->_actionFlag->expects( $this->once() )->method( diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php index 26fd8fd928c56..0f08e5c569dfc 100644 --- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserEditObserverTest.php @@ -146,7 +146,7 @@ public function testExecute() $message = __('The account is locked. Please wait and try again or contact %1.', $email); $this->messageManagerMock->expects($this->exactly(2)) - ->method('addError') + ->method('addErrorMessage') ->withConsecutive([$message], [__('Incorrect CAPTCHA')]); $this->actionFlagMock->expects($this->once()) diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php index 19dc096b9ef66..0499ec3255c51 100644 --- a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php +++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginObserverTest.php @@ -145,7 +145,7 @@ public function testExecute() ->with($customerId); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('Incorrect CAPTCHA')); $this->actionFlagMock->expects($this->once()) diff --git a/app/code/Magento/Customer/Controller/Account/Confirmation.php b/app/code/Magento/Customer/Controller/Account/Confirmation.php index a3e2db0207630..59def8640328c 100644 --- a/app/code/Magento/Customer/Controller/Account/Confirmation.php +++ b/app/code/Magento/Customer/Controller/Account/Confirmation.php @@ -1,21 +1,26 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Customer\Controller\Account; +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Controller\AbstractAccount; +use Magento\Customer\Model\Session; use Magento\Customer\Model\Url; +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\App\Action\Context; -use Magento\Customer\Model\Session; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\State\InvalidTransitionException; use Magento\Framework\View\Result\PageFactory; use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Api\AccountManagementInterface; -use Magento\Framework\Exception\State\InvalidTransitionException; -class Confirmation extends \Magento\Customer\Controller\AbstractAccount +/** + * Class Confirmation. Send confirmation link to specified email + */ +class Confirmation extends AbstractAccount implements HttpGetActionInterface, HttpPostActionInterface { /** * @var \Magento\Store\Model\StoreManagerInterface @@ -91,11 +96,11 @@ public function execute() $email, $this->storeManager->getStore()->getWebsiteId() ); - $this->messageManager->addSuccess(__('Please check your email for confirmation key.')); + $this->messageManager->addSuccessMessage(__('Please check your email for confirmation key.')); } catch (InvalidTransitionException $e) { - $this->messageManager->addSuccess(__('This email does not require confirmation.')); + $this->messageManager->addSuccessMessage(__('This email does not require confirmation.')); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Wrong email.')); + $this->messageManager->addExceptionMessage($e, __('Wrong email.')); $resultRedirect->setPath('*/*/*', ['email' => $email, '_secure' => true]); return $resultRedirect; } diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 1287dbe5df708..34f1e5e2da467 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -394,7 +394,9 @@ public function getSharingConfig() public function authenticate($login, $password) { $this->loadByEmail($login); - if ($this->getConfirmation() && $this->isConfirmationRequired()) { + if ($this->getConfirmation() && + $this->accountConfirmation->isConfirmationRequired($this->getWebsiteId(), $this->getId(), $this->getEmail()) + ) { throw new EmailNotConfirmedException( __("This account isn't confirmed. Verify and try again.") ); @@ -415,8 +417,9 @@ public function authenticate($login, $password) /** * Load customer by email * - * @param string $customerEmail - * @return $this + * @param string $customerEmail + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function loadByEmail($customerEmail) { @@ -427,8 +430,9 @@ public function loadByEmail($customerEmail) /** * Change customer password * - * @param string $newPassword - * @return $this + * @param string $newPassword + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function changePassword($newPassword) { @@ -440,6 +444,7 @@ public function changePassword($newPassword) * Get full customer name * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function getName() { @@ -462,8 +467,9 @@ public function getName() /** * Add address to address collection * - * @param Address $address - * @return $this + * @param Address $address + * @return $this + * @throws \Magento\Framework\Exception\LocalizedException */ public function addAddress(Address $address) { @@ -487,6 +493,7 @@ public function getAddressById($addressId) * * @param int $addressId * @return Address + * @throws \Magento\Framework\Exception\LocalizedException */ public function getAddressItemById($addressId) { @@ -506,7 +513,8 @@ public function getAddressCollection() /** * Customer addresses collection * - * @return \Magento\Customer\Model\ResourceModel\Address\Collection + * @return ResourceModel\Address\Collection + * @throws \Magento\Framework\Exception\LocalizedException */ public function getAddressesCollection() { @@ -538,6 +546,7 @@ public function getAddresses() * Retrieve all customer attributes * * @return Attribute[] + * @throws \Magento\Framework\Exception\LocalizedException */ public function getAttributes() { @@ -591,7 +600,8 @@ public function hashPassword($password, $salt = true) * Validate password with salted hash * * @param string $password - * @return boolean + * @return bool + * @throws \Exception */ public function validatePassword($password) { @@ -805,6 +815,7 @@ public function isConfirmationRequired() */ public function getRandomConfirmationKey() { + // phpcs:ignore Magento2.Security.InsecureFunction return md5(uniqid()); } diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer.php b/app/code/Magento/Customer/Model/ResourceModel/Customer.php index 94196df6fe093..1477287f79f4b 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Customer.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Customer.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Model\ResourceModel; +use Magento\Customer\Model\AccountConfirmation; use Magento\Customer\Model\Customer\NotificationStorage; use Magento\Framework\App\ObjectManager; use Magento\Framework\Validator\Exception as ValidatorException; @@ -42,12 +43,19 @@ class Customer extends \Magento\Eav\Model\Entity\VersionControl\AbstractEntity */ protected $storeManager; + /** + * @var AccountConfirmation + */ + private $accountConfirmation; + /** * @var NotificationStorage */ private $notificationStorage; /** + * Customer constructor. + * * @param \Magento\Eav\Model\Entity\Context $context * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite $entityRelationComposite @@ -56,6 +64,7 @@ class Customer extends \Magento\Eav\Model\Entity\VersionControl\AbstractEntity * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param array $data + * @param AccountConfirmation $accountConfirmation */ public function __construct( \Magento\Eav\Model\Entity\Context $context, @@ -65,15 +74,19 @@ public function __construct( \Magento\Framework\Validator\Factory $validatorFactory, \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Store\Model\StoreManagerInterface $storeManager, - $data = [] + $data = [], + AccountConfirmation $accountConfirmation = null ) { parent::__construct($context, $entitySnapshot, $entityRelationComposite, $data); + $this->_scopeConfig = $scopeConfig; $this->_validatorFactory = $validatorFactory; $this->dateTime = $dateTime; - $this->storeManager = $storeManager; + $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() + ->get(AccountConfirmation::class); $this->setType('customer'); $this->setConnection('customer_read', 'customer_write'); + $this->storeManager = $storeManager; } /** @@ -144,7 +157,13 @@ protected function _beforeSave(\Magento\Framework\DataObject $customer) } // set confirmation key logic - if (!$customer->getId() && $customer->isConfirmationRequired()) { + if (!$customer->getId() && + $this->accountConfirmation->isConfirmationRequired( + $customer->getWebsiteId(), + $customer->getId(), + $customer->getEmail() + ) + ) { $customer->setConfirmation($customer->getRandomConfirmationKey()); } // remove customer confirmation key from database, if empty diff --git a/app/code/Magento/Customer/Model/Session.php b/app/code/Magento/Customer/Model/Session.php index 5900fed218edf..047327a0b6c29 100644 --- a/app/code/Magento/Customer/Model/Session.php +++ b/app/code/Magento/Customer/Model/Session.php @@ -10,6 +10,7 @@ use Magento\Customer\Api\GroupManagementInterface; use Magento\Customer\Model\Config\Share; use Magento\Customer\Model\ResourceModel\Customer as ResourceCustomer; +use Magento\Framework\App\ObjectManager; /** * Customer session model @@ -17,6 +18,7 @@ * @api * @method string getNoReferer() * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Session extends \Magento\Framework\Session\SessionManager @@ -107,6 +109,8 @@ class Session extends \Magento\Framework\Session\SessionManager protected $response; /** + * Session constructor. + * * @param \Magento\Framework\App\Request\Http $request * @param \Magento\Framework\Session\SidResolverInterface $sidResolver * @param \Magento\Framework\Session\Config\ConfigInterface $sessionConfig @@ -118,7 +122,7 @@ class Session extends \Magento\Framework\Session\SessionManager * @param \Magento\Framework\App\State $appState * @param Share $configShare * @param \Magento\Framework\Url\Helper\Data $coreUrl - * @param \Magento\Customer\Model\Url $customerUrl + * @param Url $customerUrl * @param ResourceCustomer $customerResource * @param CustomerFactory $customerFactory * @param \Magento\Framework\UrlFactory $urlFactory @@ -128,6 +132,7 @@ class Session extends \Magento\Framework\Session\SessionManager * @param CustomerRepositoryInterface $customerRepository * @param GroupManagementInterface $groupManagement * @param \Magento\Framework\App\Response\Http $response + * @param AccountConfirmation $accountConfirmation * @throws \Magento\Framework\Exception\SessionException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -152,7 +157,8 @@ public function __construct( \Magento\Framework\App\Http\Context $httpContext, CustomerRepositoryInterface $customerRepository, GroupManagementInterface $groupManagement, - \Magento\Framework\App\Response\Http $response + \Magento\Framework\App\Response\Http $response, + AccountConfirmation $accountConfirmation = null ) { $this->_coreUrl = $coreUrl; $this->_customerUrl = $customerUrl; @@ -177,6 +183,8 @@ public function __construct( ); $this->groupManagement = $groupManagement; $this->response = $response; + $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() + ->get(AccountConfirmation::class); $this->_eventManager->dispatch('customer_session_init', ['customer_session' => $this]); } @@ -216,6 +224,8 @@ public function setCustomerData(CustomerData $customer) * Retrieve customer model object * * @return CustomerData + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getCustomerData() { @@ -266,8 +276,14 @@ public function setCustomer(Customer $customerModel) \Magento\Customer\Model\Group::NOT_LOGGED_IN_ID ); $this->setCustomerId($customerModel->getId()); - if (!$customerModel->isConfirmationRequired() && $customerModel->getConfirmation()) { - $customerModel->setConfirmation(null)->save(); + $accountConfirmationRequired = $this->accountConfirmation->isConfirmationRequired( + $customerModel->getWebsiteId(), + $customerModel->getId(), + $customerModel->getEmail() + ); + if (!$accountConfirmationRequired && $customerModel->getConfirmation() && $customerModel->getId()) { + $customerModel->setConfirmation(null); + $this->_customerResource->save($customerModel); } /** @@ -354,10 +370,11 @@ public function setCustomerGroupId($id) } /** - * Get customer group id - * If customer is not logged in system, 'not logged in' group id will be returned + * Get customer group id. If customer is not logged in system, 'not logged in' group id will be returned. * * @return int + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getCustomerGroupId() { @@ -407,6 +424,8 @@ public function checkCustomerId($customerId) } /** + * Sets customer as logged in + * * @param Customer $customer * @return $this */ @@ -420,6 +439,8 @@ public function setCustomerAsLoggedIn($customer) } /** + * Sets customer data as logged in + * * @param CustomerData $customer * @return $this */ @@ -521,6 +542,8 @@ protected function _setAuthUrl($key, $url) * Logout without dispatching event * * @return $this + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ protected function _logout() { @@ -567,6 +590,8 @@ public function regenerateId() } /** + * Creates URL factory + * * @return \Magento\Framework\UrlInterface */ protected function _createUrl() diff --git a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php index 7efc61af800d3..8565790990df1 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/SessionTest.php @@ -66,7 +66,7 @@ protected function setUp() $this->urlFactoryMock = $this->createMock(\Magento\Framework\UrlFactory::class); $this->customerFactoryMock = $this->getMockBuilder(\Magento\Customer\Model\CustomerFactory::class) ->disableOriginalConstructor() - ->setMethods(['create']) + ->setMethods(['create', 'save']) ->getMock(); $this->customerRepositoryMock = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -192,15 +192,12 @@ protected function prepareLoginDataMock($customerId) $customerMock = $this->createPartialMock( \Magento\Customer\Model\Customer::class, - ['getId', 'isConfirmationRequired', 'getConfirmation', 'updateData', 'getGroupId'] + ['getId', 'getConfirmation', 'updateData', 'getGroupId'] ); - $customerMock->expects($this->once()) + $customerMock->expects($this->exactly(3)) ->method('getId') ->will($this->returnValue($customerId)); $customerMock->expects($this->once()) - ->method('isConfirmationRequired') - ->will($this->returnValue(true)); - $customerMock->expects($this->never()) ->method('getConfirmation') ->will($this->returnValue($customerId)); From bdd053a9c0781a1fbf4e397a78dd33bb06bb5bf6 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Thu, 12 Sep 2019 12:43:32 +0300 Subject: [PATCH 762/841] MC-11557: Check importing of configurable products with images present in filesystem --- .../Catalog/Test/Mftf/Data/CategoryData.xml | 7 + .../Test/Mftf/Data/FrontendLabelData.xml | 4 + .../Test/Mftf/Data/ImageContentData.xml | 5 + .../Test/Mftf/Data/ProductAttributeData.xml | 22 ++ .../ProductAttributeMediaGalleryEntryData.xml | 13 + .../Mftf/Data/ProductAttributeOptionData.xml | 17 ++ .../Catalog/Test/Mftf/Data/ProductData.xml | 36 +++ .../Test/AdminMassProductPriceUpdateTest.xml | 6 +- .../AdminOpenExportIndexPageActionGroup.xml | 15 ++ ...mportConfigurableProductWithImagesTest.xml | 222 ++++++++++++++++++ .../Mftf/Data/ConfigurableProductData.xml | 15 ++ .../AdminProductFormConfigurationsSection.xml | 2 + .../export_import_configurable_product.csv | 2 + .../export_import_configurable_product_2.csv | 2 + 14 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml create mode 100644 app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml create mode 100644 dev/tests/acceptance/tests/_data/export_import_configurable_product.csv create mode 100644 dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 13951a0d197d1..bc013eedb0f4d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -117,4 +117,11 @@ <data key="is_active">true</data> <data key="include_in_menu">true</data> </entity> + <!-- Category from file "export_import_configurable_product.csv" --> + <entity name="CategoryExportImport" type="category"> + <data key="name">CategoryExportImport</data> + <data key="name_lwr">categoryExportImport</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml index a2bdaa7dbc62f..e4ffdbde4368d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml @@ -20,4 +20,8 @@ <data key="store_id">0</data> <data key="label" unique="suffix">attributeThree</data> </entity> + <entity name="ProductAttributeFrontendLabelForExportImport" type="FrontendLabel"> + <data key="store_id">0</data> + <data key="label">attributeExportImport</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml index 1f4b1470098e2..ac86deb671ed1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml @@ -18,4 +18,9 @@ <data key="type">image/png</data> <data key="name" unique="prefix">magento-logo.png</data> </entity> + <entity name="MagentoLogoImageContentExportImport" type="ImageContent"> + <data key="base64_encoded_data">iVBORw0KGgoAAAANSUhEUgAAAP8AAAEsCAYAAAAM1WX/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACY7SURBVHhe7Z0LlBxXnd6HXWyWhx9g8HS3pJFGPVU1klmC2RB28frBIbY00y2NpBnN9GMkG4JJcOJsgCVrszZg56wJuxj2BTjLY8FsADsb20kwmEc4WYgNxPYuiwX2yg9ZxrKsefS7R5IlufP/qm+N76hbo5qa7p6qru875zs6M5quvvd/76/q3lt1/9VDhU/7Ll79ikrauL66y8pVUsZ1ezf1nan+i6KoblV+bP22uUlrT+1dG2vlycEa/pWfH5kej4+oP6Eoqpt0aGx9vJqxvnF892Dt2JUbavmMNW/8fEx+X81aXy9MxNerj1AUFWQ9eGHPy4sp4wOHJ60ZXOWLWauW08CH8TN+r0YBM8W0+f47B3vOUIegKCpoyo/HL6tkzAcA9eFdgw3Qn2z8/5z8Hf6+krEeyE/EL1WHoigqCHo+2X9+MW18dm5y8MSLV25sCvrpfEI+V520TuA4h4b6etWhKYryqwTWXTJ3fxpXbyzone5qfyrjc+WsjALeLSeBrPl0MWNOqq+gKMpPmh3rf2Mpbdxbu2pj7ejuDZ6hP9k4Do6HEUQpY34zl+r/TfWVFEWtpH52ee+rZF7/kbmsVQb4hZPgbZVxXBx/btIqldLmjQeSkVeqIlAU1Wnl0gNJAf/nGOJXlzHEd2scH99jLwhmzX8sTgwkVFEoiuqEcjvW9pWzxleO7hqsHT/pnn2njO/F95cz5pefk/KoolEU1S5VM+Y1law1hatvqck9+04Z34vvr48CrEMolyoiRVGtVG5n/0Vylf07zLvd3LPvlFEOlAflkqnA/5kdX/92VWSKopajZ8Zir53LGrdWJ61jL161oW0Lest1fUFwQ60i5ZxLG7fuT/Sdq6pAUdRSVU4ZqXLGenK59+w7ZZTP2SxUzlpPlNPGhKoKRVFuND22zpQh9N+euHJD7YXdK7Ogt1yj3Ch/JWv97fSOPktVjaKoZsK++krGuK6cNUu4emKzTTOwgmJns5CcyIqVtPkH9zJvAEU1amZi3eXVjPkQYMHmGr8P8d0a9XA2C1Wz5oOFsXX/UlWZosKt6a1rotWs9XmsmGOY3AygbjHqd3hysFbNWH+1X+qtQkBR4VMpZbynOmkewFWxlO2eq/2pjPqhnvboJms+i/qrUFBUOFRI9f+WdP7v4tbYkRZuwgmKUV/UG/WXqcB3Cjvjb1Ghoaju1L6RtedUM8YtMsw/2s5NOEGxs1moOmkdKWeMjyM+KlQU1T3KTxjb57LWL+3V7wDcs++UEQfEoz4VsH6RT63fpkJGUcFWPXGm+V+PyzD35MSZ9EI7iUQrGetriJsKIUUFS0icmU8ZH0BCTFzVmiXOpBca8XGeDTictaaLqYH/gDiqkFKU/1VKxy+Zy1h24sxuumffKSNe9mYhiV81a92fG4tfokJLUf7UoZ19vcWM8ZnqMhJn0guNONqJRFPGXyIxqQo1RflHFSTOnLT24WoVhE04QTHiaG8Werf9bMBTchLYrUJOUSurfCr+5lLG+taLV25oaeJMeqER13oi0Q21Ytr4Vn7nwD9TTUBRndWeS899dTltfFSu9hXes++cnWcD5iTuiP/By3tfpZqEotqvysRAopOJM+mFRrz1RKKVlDGsmoai2qN64kzzS0d3r1ziTHqh0Q5HdtmJRP86t7N/jWoqimqdyinj31Wz1iFcbVYycSa90GgHJ5GotM/zTCRKtUzFifjvyBDfTpyJKwyh96fRLmgftBMSiRbT639bNSFFLU3PXBF7bSVdT5yJ3WfNOhztPzuJRKXdXqhkjE8ykSi1JBVSRqqStR7nPftgGu3lPBuAdkR7qqalqOY6OBrfKJ3l7iAnzqQXGu2IRUEZBdw1s3XNBtXUFFXXnrHBM2SIf305Y5ZxtecmnO4x2lFLJFpCO+8ZHDxDNT0VZs2MDlxezVh24sxqiBb0AASemMO/zf6/24x2RfvW7woMPjjDRKLh1dT2NZG5tHkbEkqeCNEmHEAA6Oek3vm0+Rh2z4XtsWS0t51INGv+F2zGUl2CCoNKE8bV1UkrNIkzYdSxrJJnYgNSLjWQrSUjr5wZj++qZq2n8Xv8f1hi8VIiUetXTCQaAk3vjL9FzvahTJyJ9/xVZIg/lzH//Int/W9QIbF1UH6uZI2/kJOA/XfNPt+NRvs7iUQlLt/Jj8cvVCGhukUzm/vPKmaMj8sV70iYNuGgc9svzJA6y0nv/unR+KUqJE01PR6/rDpp3m9fEUO0/uFsFqpI/yimjFv2Sn9RIaGCrPzEeiTO/AU6dFgSZ6KO8yvcGTNXThkfuGN7z6+pkCyqj17c8+tyovygjBJy+HxY7nygjkwk2iWa2rbOqGSsO5AQMmyJM+uJMDfgzTjfOLTFWyLMqR39A3LiuBPHCWf87ESi30AcVEgovwsJH4sTxgfDljhTv3LJVfuX+fTADhWSZamUGhiVk8Cj9nFDOHJCP5KpwAd+ICMiFRLKj8qND1wcxsSZzpy1nDXx8otbprec8xoVkpZoZvPZZ1UzxsflpBLONZN32W8Wuh/9S4WE8ouwWl1MG39RnbSOvyids1lDdqPROZ3VagHzu+1+7RVeM1bJGN8L590S3CIdPI5+hv6mQkKtpJDQ8XDIEmeijs596nLGPFDp8H1qGQVcXZXvxfeH6jkJNa2qZq19chLYpcJBdVpI4FhKG6FMnGm/6lqGo5W0eRte8a1C0lHNjK2OyRTjr7CHvttfLa4b/cxJJCr9716ZCrxJhYRqtw4kI68sp4yPze2y7E04YZp/Os+mlzLmQ4Xx+BUqJCuqQmr9JinPw/YVMSQjL9hZZ5nLWmUkEkW/VCGh2qFieiCJhI2h62hq5bmaNYtytf+DvZt6zlQh8YX2buo7s5IxrkP57BOylLdZPbrN9glZTQWQ0LWYGUiokFCtUn5i7dpqxrr9qFz5wpQ4E50L+9ExrC6nzXtmR+MbVUh8qdlUfGM5Y/4PJx9CWE7OMPol+mc5bX0lP7J2rQoJtRzJvPLasCXORB21xaXHZVg5ocIRCMm0DJmQnkAmnXAtwjojNOsQEr6qcFBLVXE8/rZKxrITZ2JxKyxXEWcuiRx0hbRxq8wlz1MhCZRQ7lLa+BTqgfo0q2s3Gv3Ufsmo1BmJRGfHmEjUtZBwsZoxPilXjtB1Gjv7rP1AidU1nWYW2Y8n5SQu9QrfSdx+/uKFatb8k30ja89RIaGaqTAxkLaHizJ0CuNw8fCkdbCcMq+pqXh0i27o6XlZQYbBc1nr+bBO36RfM5FoM81MGIMSnLvCmDgTC0Uv7FZvnMms7VMh6Uqphdsvo75hWriFnYXbSsb879M7DEuFJLy6F7eI0sb1SKyIs2OYNuE4t4jkavgPsyF711xR6iv1/hnqH5Zbtqijs1mojESiGeM69H8VknBpdjx+hcyFHrQBCNtc0O70VjXMb5nFW47lJHBTNWvNIR5helirvlkIdwXMB5FAVoWk+4XEmdWM+QUs/oT1sVC56n0zlzJ+U4Uk1Do0PvAmice9oX1MW0Y+1bT1eXChQtKdKqSN91YnQ7ghRG3Ckavc0/kJ40oVDkpTfnzgKonPfsQpnIlEzWcLE8bVKhzdI2zCkcp9J6yJM6VT42r/508m+89XIaGa6HmJTyVj/aVMicKbSHTSvC8/Gn+zCklw9eTYurPLKeMW6fyHcc8+TPM650EPmdfdPzUWv0SFhHKh6Yn4pQJBaJOyVLODh8tp84/AjwpJsITEmQL9L9GAoUycmbVmkTiT6Z+8CXGrMJFosBKJHhpbH0fiyOOhTpxpfu35HX39KiTUMnRoIr5eLiJfC3MiUan/1wsSBxUS/8lOnClXusMhT5xZmjC2q5BQLRQSkoY+kWjafD84UyHxh/Lj8cukYew5Whg34eBlD9WMccujLU6cSS3U9BbzNSqR6NGwrSFpzwbcn59Y/OUrHRFWZ4tp47Nzk4MnXgzZyy6d1VkZkn13Zqz/rSokVAdUSPe/VaaW9USiu0J290g4q05aJ8op4zPgT4Wks0ICQ+n49Rc8hmgYNp84M2s9V8p04X3ZAKmUNt4ro4CDaI/QJRJ9tz0KeLqYMSdVONqv2bH+NyJxIYZdYU2cicSV0ylzRRJnUgs1M2bEymnj8+F9YtTO6fjNXKq/fU+M/uzy3lfJvP4jc1mrHNb5VhmJMydC9Cx2gFRIx6+Q9rETiYbx2YC5SatUSps3tjyRaA6JMzPmzxHYMCXOdFZaZY5VkCH+dX5LnEkt1A8uXv0KGZVeLyAU0G5ov2bt2m0Gj/OJRLPmPxYnWpBINLdjbV85a3wlbIkz4fnEmRnjLuQbUCGhAiC0l0wF7kb7hTE/hJ1INGN++TnhV4VkaapmzGsqWWsKZ5PQJs6cMMdVOKgAqpA2JtCOaM8wZoYSfg+BYxWO0yu3s/8iOWuEMnFm/X1s1jEZOt5aGIu9VoWECrCeHYm+TqZsn0a7on3DtFbl7C+xE4mOr3+7CkmjnpHOPpc1bq0HaUP4gvQupFiy/m7RIFGBVS5tXlS1s0GHNJGocD0nFzUkyFUhqQt54csZ68nwDY/qQ/w5GR4VJox/i0STKiRUF+qOnp5fU4lE7fdAhOkRdGc6W85aT8y/ByI3MXDHcRkahHJhROosQ/yvYGHTDgYVCiGRqEwFbg9lIlGpL3gH9z3Ht6+pFXbG6/8pV8KT/7ibjDOgljjzZ8VxY0j1ByqEQuJU9AP0h1DcwlZ8g3dw3zM7FKnZ3rq6lhuXkwD+oAtPAvbcB42ctSphTpxJLdSesXNfXcZbnyetStc+vKaYBt/g3GG+Z3Y4WrPtnAS29dXyKUN9oMmBAmaczecTZ05a/2t2vP8C1e4UNS88ti6jgO5KJAp+wbHwDK7nGVfMvwS/Y/Wfue1ra/m0Wf9wswP73PoiR3XS2ldMx3epdqaoU6qYMnZ3xYY1cCv8gmOda92N8DvGHyditdxo/0sHO/kLfOwT9tbHwVolY3zmudH1r1dtS1GnFfpLJWt9Zk76T+A2CylOwS34bQa941PDD+OD8JZVtRwWBXFgH58EcJaef7AhYz4wvSN+mWpPilqypsf63yH96MeBeOBNsQlOwes8u824Vl4cfsfqQLmta2r58QHfnQTQKM4mHDlj54oZ44NMnEm1QrchPV3a+H3pV3n0L989G+CwKFyCTzfQO3YHv2N10Ny2tbWcsyjYrEAd9nzizKz5tUPbfJzokAqs7MS0WevreC7AN4lEhT9wCB51Pt16afA7xpdgPWDHuhVbFMTZ10mcKY3CxJlUR2QnEp1c4USi4A2LecLf6eb1i9kb/I7xpUk5CYytf6lQJxe0Da4/r2xDf1iu9n/ExJlUJ7V389lnlTPGx/FyjY4+G6D4Am/gziv0jpcHP4wCwFva/5AQzrK4B2tvVMia353eGX+Lag+K6rimR/t/q+IkEm3nswGKKfshHZeLeW68fPgd2wWSf0fW1PITrV0URFDnE2dmzAOllPEeFf/QSaY3g7WxwTPUj77Q3k19Z6Jc6sfQqZoxrm7LC2gdhoQncGXz1QLoHbcOfseqcDn7ScHWrAfgXuuRXYO1ctr6/MzY6piKeah0aGdfbylrfTmXMp6ckWGn+rUvlHvnWWdLuZ4qZ80voZzq16HSzLboKrkwfQH9tCXPBoAbezGvbwFXrXTr4XeMwi5jURBnTydxZiljPlwYj1+h4hw6VVLWu8oZ61e1q9+IWD7+zGjsN9R/+UJIGjmbMp9C+UpSzkp64Cr1X6FTJR2/opQ2/x791lMiUXDSgsU8N24f/I5R+OSqJS0KOvfs5yatfCVtXI/EjCq2odLUePzCatb8NuaUWOuA82njsZZnaF2msEkqlzL3Yt7rrMlUs9a9U93wqmkP2if9Vfrth5eUSFRxUV/MU/P6Zjy10O2HH0ZFxDkXi4J64sywziORJr2aMf9T5aRXmwcBfpTTuRtTxt2YtHmz38rbKc2OxjeeNpGoYgFcgA+HlaYctdidgd+xU7GRNbXchLNzsD400l52+bjMnUKbOLM0Ed9azVh77FicdB85KPDDepvKKOCRXCq+Rf156FRIGSmJweN4y878ZiGn7wsH9cW8zkHvuLPwO1aVxI4j6TS1Oek0MkQ6WkoZn0TCRRWzUCk/tmadDPH/BleIUz1BFiT4daM+yJojo5mv5kfWrlUfC5Wkzc5DYlj0c/R39PvFdtx1wisDv2OpdHnLqlppbN3B2XEjlIkzsQehOGG+v5I1T/tq86DCj/q8tPfCnC6nzN9DPj318VAJiUQr0t/R71cKescrC7/4ha2x2vRQ5CcqNqGSDPl+V672/xdQuNk1FlT4HaN+zq5LGQb/KLfTvEgdIlSaHer9Kfp9Mx466RWH/6gEYWY48pCKSyj0mAwBKxnzzypZ6wTSpDcDpZmDDr9u5NAvS/0rafNPwzTVq4lnhyMPo98346GTJvwdVmFiIF2dtJ7A1X6pmWK6CX7U28m0JPPgx2dTRkodrqtF+DWHBX7kDpzLmPcgRxwW9ZYCveNugt8x4uDc3pXR0D2zqfhGddiuFOHX3O3wA9RCeuBGmeOWcZUruHng4xTuRvgdIy6IT3XSLEm8bnjIZ3VslQi/5m6Gf3Zi/WYZ0tqPerYiL3w3ww8jPtp7FR6eHVu/SX1F14jwa+5G+PFq5HLG/GKrX23e7fDrRtywSaYkcczt7F+jvirwIvyauw3+UnrgfXLVeg5Xr5Zu7xSHCX7Ebf5dipPWgVJq4F+rrwu0CL/mboFfhqi/Xc2a/9tO7LCrPYkdwgS/Y8QR8bQ3C2Ws7xfH429TXxtIEX7NQYd/38jac0pp84/l6nSk3Smdwgi/4/nUbRLnUsr8xJNj685WXx8oEX7NQYa/PGGNVTuYzDHM8MOIr7ZZ6JcyFRhVRQiMCL/mIMI/vcOwyhnzv9lpnNvc4XWHHX7d2CyE+JfT5p3TY5apiuJ7EX7NQYL/wQt7Xl7JmB+Sq878Cxyadcx2mfAvtLNZSNojV0kbv4/2UUXyrQi/5qDAXxg33ilX+/9nrz57Sc/UAhP+RqMdnHRv5Yz103LKfIcqli9F+DX7Hf79W9dEK1nzs/ZLG5ewCacdJvyLG5ukqpPWCbyc9entayKqeL4S4dfsZ/grafNflbPms7iqtPqevRcT/sWN9nGeDShlzGdlivZuVUTfiPBr9iP8eBmInjhzpaF3TPjdGe2FWKlnA76FRKiqqCsuwq/ZT/Dnd7/pHJnXNyTO9IsJ/9LsPBsg7TlXTps3o31VkVdMhF+zn+DP7ej/cO09F9SqbXpCb7km/Es32hHtiXadGe2/XhV5xUT4NfsJ/unNvTfXRvtquZ2dffGoWxP+JVq1H3Lh10bX1KR9b1JFXjERfs1+gn92KPrR2og0ylCkltu6ppYbb+07B5drwu/Sqs3Qfrmt9Vz4aFfpZx9RRV4xEX7NvoN/26p62VRmVbwrLZdy3jHQpKN10IT/NEb7APoJo5YbWfiOO7Qr4V9owq9pAfyOnZPAdm/vHGylCf8iRrvgHXfSTnq7OSb8jSb8mprC7xidCS8eHe1/qbOd3AHbbMLfxKod0C6LvdiS8Dea8GtaFH4YHQvesqq+KKiGmQ0dsk0m/JoRd4k/2gHtMd82zdpNTPgbTfg1nRZ+x05H27pa5pedWxQk/GIVa8Qd8T8d9I4Jf6MJvybX8DtWnQ6LgnlnUbBZh22RQw8/4itxRrz1+Lsx4W804de0ZPgdOyeBHe1dFAwt/IgnFvMkvnq8l2LC32jCr8kz/I7RKZOxWm6sPYuCoYNfxQ/xRFy9QO+Y8Dea8GtaNvwwOii8ZXUttzNe78AtOgmEBn4VM8QPcZyPabN4uzThbzTh19QS+B2rDpsbWWM/dNKKk0DXw+9APzFgx60V0Dsm/I0m/JpaCr9j1Xlz29Yue1Gwq+FHXOzFvLUL4tYqE/5GE35NbYHfsXMSsBcFVWdvBsEi7kr4EQeJx3IW89yY8Dea8GtqK/yO0bmTq+ydZvOd/2QgTuGugl/VG3FAPNoFvWPC32jCr6kj8MPo6DAWBcfdLwp2Bfyqrqh3qxbz3JjwN5rwa+oY/I5Vx8fiVn5+UbAJMMqBht95FNrecdfaxTw3JvyNJvyaOg6/YwVBbjsWBU/9kFBg4Ud9pF6on17fTprwN5rwa1ox+B0DikV2DgYOflX+3Oi6RXfcdcKEv9GEX9OKww8PwQIJFgXxkJAGUWDgd6DHQzrOYh7q1ay+HTLhbzTh1+QL+B3bwIixc1AtCh69cqO/4Uf5pJz2Yt4Sdtx1woS/0YRfk6/gd+wAtK2vdnQSw2nznwCbKrIvdOjSc1+dy1iPH8ladjn9BL1jwt9owq/Jl/A7FpiObJWpwLa+R2pjg2eoIvtCezf1nZnbtmbPYSmf36B3TPgbTfg1+Rp+sR2rod4HVXF9pdmh3of80KFPZcLfaMKvKRDw+yRWuvzUoU9lwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt/onmNbV9UKiagEpvkftNuE370Jv3cT/rrBOXgH9z0zQ9HPVZKx2gsrVBjC796E37sJf93gHLyDe7tAUphNpUT04drIqloZ/9HkQ+0y4Xdvwu/dYYYfPINr8F1KxB4C7/XSKO27ePUrSsnYHxYT0fKL8kd59aFmB2ulCb97E37vDiP84DcvQ3zwLBf3Umk48uEfCOf1kjTRbKL3AjlL3I3hwZEt7S8g4Xdvwu/dYYQf/ILjciJ218Et52+sl8CFColoRkYB+xA0+dc+izT7guWa8Ls34ffusMAPTsGr4vap2WQ0Xf/mJepAMnKeTAU+XU5GXzjepsISfvcm/N4dBvhz4uMjsVpJeBVuP/WY8Fv/1mUol4xcVEzEfoS5Q7XFC4KE370Jv3d3M/zgsSpDfPBZTER+ODsUeXv921qkG3p6XiYjgGtl/jCFQGIhoRUnAcLv3oTfu7sRfvAHDlE34fJQcTh6LTitf1MbdHBL77ricOyrWExoRQUIv3sTfu/uRvidRflSInp7ftMb1ta/oQMqJqJbxHvss84ypgKE370Jv3d3C/zgzL5nL/UR/h4pJqPJ+pE7rL2bzz6rOBS9uZKIVU7IfAMLDs0KvJgJv3sTfu/uBvjBF+b1MsSvCPgf23Ppua+uH3UFld8ce3N+OHLfiZFY7bAMQ5YyCiD87k34vTvo8IMrrOTLHP9bU8Jb/Wg+Un4odlUlGd2vhiSuTgKE370Jv3cHEX7wA45Q9nIiuj+XiO2uH8Wnmtq0JiKjgNuweQA7h5pVSjfhd2/C791BhB/8gKPcUPRzh4bO660fIQDKD/W+o5yM/gRBt3cRNakcTPjdm/B7d1DgByfgxd5kl4j+OD98/mX1TwZMezf1nSlzlA9JJXIIfrNnAwi/exN+7w4C/M49e5k6z+aHYx/cMzZ4Rv1TAdb0pqgpo4A7m20WIvzuTfi92+/wH1XPzQgnd0wNRY36X3eR8oneUTmrPYaGKCXrowDC796E37v9CD/6f0nds5fR8aP5ZGRH/a+6VPsTfeeWEtE/LiaiR/FsAEYDhN+dCb93+w1+9Hv0f2HhSCER/cTPhYv6X4RAM1esfmslEf1+bXR1TaD7hfr1iorwexPhX5okVo+i30v//9705t5/rn4dLqHTlJOR908PR+6rXX3hy+u/XTkRfm8i/O5VGxs8Q2J1nwzzf0/9Ktya3nLOa2oX9/y6+nHFRPi9ifC7F/o5+rv6kfKLCL83EX4q8CL83kT4qcCL8HsT4acCL8LvTYSfCrwIvzcRfirwIvzeRPipwIvwexPhpwIvwu9NhJ8KvAi/NxF+KvAi/N5E+KnAi/B7E+GnAi/C702Enwq8CL83EX4q8CL83kT4qcDL7/Aj+8v0cPQnqri+kpTvpyjfyWX2iwk/taj8Dj/e0VZIRA9ODUcuVkX2hXKbey8pSrlQvmbl9oMJP7Wo/A6/k9O9lIgeKyVif3rgksh5qugrouc29b5eyvFnUp7ji72bwQ8m/NSi8jv8MABzXuEk0D1ZHI5kVfE7qmIyMinf/xTK4fbVbCtpwk8tqunN0Zv8Dr9uvAPhmMyzy8noPQcv771AVaOten7oDW8sJ6L/89hI4zsY/Gy7XYd6P6aqQVELJVf+j9XGVmNe7fsrmWPntc1yFS6XEpEb9l28+hWqOi3VA2+L/UZpOHKjjDQq+D4vr2NfCaMd0Z5oV175qVNqZnh1TK6iX6jKFc3PK9cnGx0ci232VCAZ/fupzb1Dqkot0dRw77Ac9x9wfHxPUE6MMNqxIu1ZGI58YXrrmqiqEkU11+xwZFM5EXsoqJ29KmWWofkXf7U5ukpVyZOevSK6Wo7zpSCfDNGOU0ORzapKFHV64SWjpUTshqIMpwM5zMUoIBE9WBiOvU9VaUnKDcWukc8/j+MEaRqUF6O90G6lZOQP0Y6qShS1NB1M9F5QTMTuxsLa4QAtcAHWOSmvWg/4/kxi9VtVlRbVzPCqf1FJRr+PV0nh80Ea9WABEiMUmaLcM7vp/I2qShS1POGWmlxNAnNryzFGLOqdcEcLw9E/2XNF7LWqSgv0jPy+JP+Pv8PfB2mkg/ZQ7fJkLhHNqCpRVOt0IBk5T4aSn5J58LHjI8EaBeCtyACkkog+NpvoHVNVslUY7t0pV/t/sqcK6u3JzY7jN+MEhXYoJ6MvlJKxTz/7zujrVJUoqj2Sq8vvlhORH9bkConFtSAOjWeGIt+Y2Ry5fHYoegd+DtI9e8QbcX9RwC9KO+SSkYtU01BU+1W7oedlxWT02nIiNhXERTEAjxVx/Iufg1B2lNFZzETci0PRf3+DtINqEorqrA5tfv364nDsq1gM9POW1mbOC0jNfu9X40SFOMsJ4PbnE2/oV01AUSsrmQpsKSVij9TnzcGaCvjZiKNzz76SjP08NxzZqkJOUf4RXr0sV9Oby4loNUgr5n414odblTLEr8jU5KZHf4evtqZ8rvzm2JuLyci3j0vHxTCVo4ClGfFC3BC/4nDk24inCi1FBUP5odhVMgrYjyFrkJ4NWCkjPs49e7naP50f7r1ShZKigqepTa+L5Iain0Oyi2NyJWvW6em6ER+Z35/IS7yelripEFJUsJXfsuod5WT0x3g2wO9ZbzppxAHxQFxklPRAfvj8y1TIKKp7hE0m+eHof5ROnsNCVtButbXaqL+9oJeMzs4moh/aMzZ4hgoVRXWnnt0UNYvJ6J32k3UBezagVcYzEbDM8e+YGooaKjQUFQ4VEr1jlWT00fqzAd2/IIj6OXsM5Gr/aH5z76gKBUWFT/sTfeeWEtFPBG033VKNejm7C4vJ2H9GvVUIKCrcOhjgffSLGfVAfVCvaiL6Pbd5BSgqdJpN9P4bGRo/h6FxkDYLnWyU29mEI/U5UBqOvFdVkaKoUwm592R4/EVcMbEo2AwuvxvlRvmREPWZ4dUxVTWKotxodiiyWU4CD9v3wAPwbADK52zCKSViDyERqqoKRVFLFfLwF4ajNxZ9nEgU0DuJM+VkVSokIjf8oE3vD6Co0Gk20XuBXFXvxiOwfsu6g/LYj+YmYncd3MLEmRTVFqlEovswtF7JzUL4XmcTjvz7VIGJMymq/aonEo19WubVx4+vwIIgph524sxE7JiU41OPSXlU0SiK6oRyw5GLi4nYjzDX7kQiURy/njgTV/vID0tMnElRK6cbenpeVtYSiWKzTKtPAjgejovj43uKw9Fr8b2qCBRFraScRKJYfGt1IlF7A5Ict5SI3l5g4kyK8qeKiegW8R77Kr2MqQA+59yzl+M9UkxGk+orKIryq/ZuPvus4lD05koiVvGyWcjZhCND/IoM8W96dAsTZ1JUoITEl/nhyH0nRur57t2MAuqJM/Eij8i3p5g4k6KCLSQSrSRPnUgUP+P39QW96P5iIrZbfZSiqKBratOaiFzNb7MTiW5dmEgUP+P3SDR6aOi8XvURiqK6SfmhXiQS/Qnu1QN4/CtX+x8zcSZFhUC4R19I9l5THYk9kRuOvE/9mgqVenr+P+OvTjWo+kMRAAAAAElFTkSuQmCC</data> + <data key="type">image/png</data> + <data key="name">magento-logo.png</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index 2deec6b8c1f8e..0bdc3a261053f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -376,4 +376,26 @@ <data key="frontend_label">Size</data> <data key="attribute_code" unique="suffix">size_attr</data> </entity> + <!-- Product attribute from file "export_import_configurable_product.csv" --> + <entity name="productAttributeWithTwoOptionsForExportImport" type="ProductAttribute"> + <data key="attribute_code">attribute</data> + <data key="frontend_input">select</data> + <data key="scope">global</data> + <data key="is_required">false</data> + <data key="is_unique">false</data> + <data key="is_searchable">true</data> + <data key="is_visible">true</data> + <data key="is_visible_in_advanced_search">true</data> + <data key="is_visible_on_front">true</data> + <data key="is_filterable">true</data> + <data key="is_filterable_in_search">true</data> + <data key="used_in_product_listing">true</data> + <data key="is_used_for_promo_rules">true</data> + <data key="is_comparable">true</data> + <data key="is_used_in_grid">true</data> + <data key="is_visible_in_grid">true</data> + <data key="is_filterable_in_grid">true</data> + <data key="used_for_sort_by">true</data> + <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabelForExportImport</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml index 98c9a70e6aad4..7d7f2cb7bf4be 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml @@ -30,4 +30,17 @@ <data key="disabled">false</data> <requiredEntity type="ImageContent">MagentoLogoImageContent</requiredEntity> </entity> + <!-- From file "export_import_configurable_product.csv" --> + <entity name="ApiProductAttributeMediaGalleryForExportImport" type="ProductAttributeMediaGalleryEntry"> + <data key="media_type">image</data> + <data key="label">Magento Logo</data> + <data key="position">1</data> + <array key="types"> + <item>image</item> + <item>small_image</item> + <item>thumbnail</item> + </array> + <data key="disabled">false</data> + <requiredEntity type="ImageContent">MagentoLogoImageContentExportImport</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml index fcb56cf298a98..8daa63f08d325 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml @@ -86,4 +86,21 @@ <data key="label" unique="suffix">White</data> <data key="value" unique="suffix">white</data> </entity> + <!-- Product attribute options from file "export_import_configurable_product.csv" --> + <entity name="productAttributeOptionOneForExportImport" type="ProductAttributeOption"> + <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="label">option1</data> + <data key="is_default">false</data> + <data key="sort_order">0</data> + <requiredEntity type="StoreLabel">Option1Store0</requiredEntity> + <requiredEntity type="StoreLabel">Option1Store1</requiredEntity> + </entity> + <entity name="productAttributeOptionTwoForExportImport" type="ProductAttributeOption"> + <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="label">option2</data> + <data key="is_default">true</data> + <data key="sort_order">1</data> + <requiredEntity type="StoreLabel">Option2Store0</requiredEntity> + <requiredEntity type="StoreLabel">Option2Store1</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 517ab253b8238..faad9094b8d7f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -351,6 +351,15 @@ <data key="filename">adobe-base</data> <data key="file_extension">jpg</data> </entity> + <entity name="TestImage" type="image"> + <data key="title" unique="suffix">test_image</data> + <data key="price">1.00</data> + <data key="file_type">Upload File</data> + <data key="shareable">Yes</data> + <data key="file">test_image.jpg</data> + <data key="filename">test_image</data> + <data key="file_extension">jpg</data> + </entity> <entity name="ProductWithUnicode" type="product"> <data key="sku" unique="suffix">霁产品</data> <data key="type_id">simple</data> @@ -1165,6 +1174,33 @@ <requiredEntity type="product_extension_attribute">EavStock10</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> + <!-- Products from file "export_import_configurable_product.csv" --> + <entity name="ApiSimpleOneExportImport" type="product2"> + <data key="sku">api-simple-one-export-import</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name">Api Simple Product One Export Import</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">api-simple-one-export-import</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> + </entity> + <entity name="ApiSimpleTwoExportImport" type="product2"> + <data key="sku">api-simple-two-export-import</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name">Api Simple Product Two Export Import</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">api-simple-two-export-import</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> + </entity> <entity name="SimpleProductPrice10Qty1" type="product"> <data key="sku" unique="suffix">simple-product_</data> <data key="type_id">simple</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml index 4d581bae700d7..f5ad5b8079d1f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMassProductPriceUpdateTest"> <annotations> - <stories value="Mass product update "/> + <stories value="Mass product update"/> <features value="Catalog"/> <title value="Mass update simple product price"/> <description value="Login as admin and update mass product price"/> @@ -24,8 +24,8 @@ <createData entity="defaultSimpleProduct" stepKey="simpleProduct2"/> </before> <after> - <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> - <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml new file mode 100644 index 0000000000000..83762d347a93f --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenExportIndexPageActionGroup"> + <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="goToExportIndexPage"/> + <waitForPageLoad stepKey="waitForExportIndexPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml new file mode 100644 index 0000000000000..7548c09912897 --- /dev/null +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -0,0 +1,222 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminExportImportConfigurableProductWithImagesTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Export/Import products"/> + <title value="Check importing of configurable products with images present in filesystem"/> + <description value="Check importing of configurable products with images present in filesystem"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11557"/> + <group value="ConfigurableProduct"/> + </annotations> + <before> + <!-- Create sample data: + 1. Create simple products --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct2" stepKey="createFirstSimpleProduct"/> + <createData entity="SimpleProduct2" stepKey="createSecondSimpleProduct"/> + + <!-- 2. Create Downloadable product --> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + + <!-- 3. Create Grouped product --> + <createData entity="ApiGroupedProduct" stepKey="createGroupedProduct"/> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createFirstSimpleProduct"/> + </createData> + + <!-- 4. Create configurable product with images --> + <createData entity="CategoryExportImport" stepKey="createExportImportCategory"/> + <createData entity="ApiConfigurableExportImportProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createExportImportCategory"/> + </createData> + <createData entity="ApiProductAttributeMediaGalleryForExportImport" stepKey="createConfigurableProductWithImage"> + <requiredEntity createDataKey="createConfigProduct"/> + </createData> + <createData entity="productAttributeWithTwoOptionsForExportImport" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOptionOneForExportImport" stepKey="createConfigProductAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOptionTwoForExportImport" stepKey="createConfigProductAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeFirstOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeSecondOption"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <createData entity="ApiSimpleOneExportImport" stepKey="createConfigFirstChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + </createData> + <createData entity="ApiProductAttributeMediaGalleryForExportImport" stepKey="createSimpleProduct1Image"> + <requiredEntity createDataKey="createConfigFirstChildProduct"/> + </createData> + <createData entity="ApiSimpleTwoExportImport" stepKey="createConfigSecondChildProduct"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + </createData> + <createData entity="ApiProductAttributeMediaGalleryEntryTestImage" stepKey="createSimpleProduct1Image1"> + <requiredEntity createDataKey="createConfigSecondChildProduct"/> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductTwoOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeFirstOption"/> + <requiredEntity createDataKey="getConfigAttributeSecondOption"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigFirstChildProduct"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigSecondChildProduct"/> + </createData> + + <!-- 5. Create configurable product --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigurableProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithTwoOptions" stepKey="createConfigProductAttr"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttr"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttrSet"> + <requiredEntity createDataKey="createConfigProductAttr"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption"> + <requiredEntity createDataKey="createConfigProductAttr"/> + </getData> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct"> + <requiredEntity createDataKey="createConfigProductAttr"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigurableProduct"/> + <requiredEntity createDataKey="createConfigProductAttr"/> + <requiredEntity createDataKey="getConfigAttributeOption"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild"> + <requiredEntity createDataKey="createConfigurableProduct"/> + <requiredEntity createDataKey="createConfigChildProduct"/> + </createData> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="deleteAllExportedFiles" stepKey="clearExportedFilesList"/> + </before> + <after> + <!-- Delete created data --> + <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFisrtSimpleProduct"/> + <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> + <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> + <deleteData createDataKey="createConfigSecondChildProduct" stepKey="deleteConfigSecondChildProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createConfigurableProduct" stepKey="deleteConfigurableProduct"/> + <deleteData createDataKey="createConfigChildProduct" stepKey="deleteConfigChildProduct"/> + <deleteData createDataKey="createConfigProductAttr" stepKey="deleteConfigProductAttr"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createExportImportCategory" stepKey="deleteExportImportCategory"/> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGridColumnsInitial"/> + <!-- Admin logout--> + <actionGroup ref="logout" stepKey="adminLogout"/> + </after> + + <!-- Go to Catalog > Products and choose one of configurable products: Products page is open --> + <actionGroup ref="filterAndSelectProduct" stepKey="openCreatedConfigurableProduct"> + <argument name="productSku" value="$$createConfigProduct.sku$$"/> + </actionGroup> + + <!-- Go to System > Export --> + <actionGroup ref="AdminOpenExportIndexPageActionGroup" stepKey="goToExportPage"/> + + <!-- Set Export Settings: Entity Type > Products, SKU > ConfProd's sku and press "Continue" --> + <actionGroup ref="exportProductsFilterByAttribute" stepKey="exportProductBySku"> + <argument name="attribute" value="sku"/> + <argument name="attributeData" value="$$createConfigProduct.sku$$"/> + </actionGroup> + + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCronFirstTime"/> + <magentoCLI command="cron:run" stepKey="runCronSecondTime"/> + + <!-- Save exported file: file successfully downloaded --> + <actionGroup ref="downloadFileByRowIndex" stepKey="downloadCreatedProducts"> + <argument name="rowIndex" value="0"/> + </actionGroup> + + <!-- Go to Catalog > Products. Find ConfProd and delete it --> + <actionGroup ref="deleteProductBySku" stepKey="deleteConfigurableProductBySku"> + <argument name="sku" value="$$createConfigProduct.sku$$"/> + </actionGroup> + + <!-- Go to System > Import. Set import settings: Entity Type > Product, Import Behavior > Add/Update, + Select File to Import > previously exported file and press "Check Data" --> + <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProduct"> + <argument name="behavior" value="Add/Update"/> + <argument name="importFile" value="export_import_configurable_product.csv"/> + <argument name="importMessage" value="Created: 1, Updated: 0, Deleted: 0"/> + </actionGroup> + + <!-- Go to Catalog > Products: Configurable product exists --> + <actionGroup ref="filterAndSelectProduct" stepKey="openConfigurableProduct"> + <argument name="productSku" value="$$createConfigProduct.sku$$"/> + </actionGroup> + + <!-- Go to "Configurations" section: configurations exist and have images --> + <seeNumberOfElements selector="{{AdminProductFormConfigurationsSection.currentVariationsRows}}" userInput="2" stepKey="seeNumberOfRows"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsNameCells}}" userInput="$$createConfigFirstChildProduct.name$$" stepKey="seeFirstProductNameInField"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsNameCells}}" userInput="$$createConfigSecondChildProduct.name$$" stepKey="seeSecondProductNameInField"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsSkuCells}}" userInput="$$createConfigFirstChildProduct.sku$$" stepKey="seeFirstProductSkuInField"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsSkuCells}}" userInput="$$createConfigSecondChildProduct.sku$$" stepKey="seeSecondProductSkuInField"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsPriceCells}}" userInput="$$createConfigFirstChildProduct.price$$" stepKey="seeFirstProductPriceInField"/> + <see selector="{{AdminProductFormConfigurationsSection.currentVariationsPriceCells}}" userInput="$$createConfigSecondChildProduct.price$$" stepKey="seeSecondProductPriceInField"/> + <seeElement selector="{{AdminProductFormConfigurationsSection.imageSource(MagentoLogo.fileName)}}" stepKey="seeFirstProductImageInField"/> + <seeElement selector="{{AdminProductFormConfigurationsSection.imageSource(TestImage.fileName)}}" stepKey="seeSecondProductImageInField"/> + + <!-- Go to "Images and Videos" section: assert image --> + <scrollTo selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" stepKey="scrollToProductGalleryTab"/> + <actionGroup ref="assertProductImageAdminProductPage" stepKey="assertProductImageAdminProductPage"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + + <!-- Go to any ConfProd's configuration page: Product page open successfully --> + <click selector="{{AdminProductFormConfigurationsSection.productLinkByName($$createConfigFirstChildProduct.name$$)}}" stepKey="clickOnFirstProductLink"/> + <switchToNextTab stepKey="switchToConfigChildProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <!-- Go to "Images and Videos" section: assert image --> + <scrollTo selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" stepKey="scrollToChildProductGalleryTab"/> + <actionGroup ref="assertProductImageAdminProductPage" stepKey="assertChildProductImageAdminProductPage"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + <closeTab stepKey="closeConfigChildProductPage"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml index de6714a9b959e..318791ae9453c 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml @@ -78,4 +78,19 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <!-- Configurable product from file "export_import_configurable_product.csv"--> + <entity name="ApiConfigurableExportImportProduct" type="product"> + <data key="sku">api-configurable-export-import-product</data> + <data key="type_id">configurable</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name">API Configurable Export Import Product</data> + <data key="urlKey">api-configurable-export-import-product</data> + <data key="price">123.00</data> + <data key="weight">2</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index 1defecbc7c285..58101e8173e52 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -39,6 +39,8 @@ <element name="variationLabel" type="text" selector="//div[@data-index='configurable-matrix']/label"/> <element name="stepsWizardTitle" type="text" selector="div.content:not([style='display: none;']) .steps-wizard-title"/> <element name="attributeEntityByName" type="text" selector="//div[@class='attribute-entity']//div[normalize-space(.)='{{attributeLabel}}']" parameterized="true"/> + <element name="imageSource" type="text" selector=".admin__control-fields[data-index='thumbnail_image_container'] img[src*='{{imageName}}']" parameterized="true"/> + <element name="productLinkByName" type="text" selector="//a[contains(text(), '{{productName}}')]" parameterized="true"/> </section> <section name="AdminConfigurableProductFormSection"> <element name="productWeight" type="input" selector=".admin__control-text[name='product[weight]']"/> diff --git a/dev/tests/acceptance/tests/_data/export_import_configurable_product.csv b/dev/tests/acceptance/tests/_data/export_import_configurable_product.csv new file mode 100644 index 0000000000000..97e63d06abe71 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/export_import_configurable_product.csv @@ -0,0 +1,2 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,associated_skus,configurable_variations,configurable_variation_labels +api-configurable-export-import-product,,Default,configurable,Default Category/CategoryExportImport,base,API Configurable Export Import Product,,,2,1,Taxable Goods,"Catalog, Search",123,,,,api-configurable-export-import-product,,,,/m/a/magento-logo_1.png,Magento Logo,/m/a/magento-logo_1.png,Magento Logo,/m/a/magento-logo_1.png,Magento Logo,,,"7/26/19, 8:21 AM","7/26/19, 8:21 AM",,,Block after Info Column,,,,,,,,,,,Use config,,,0,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,0,,,,,,,,,,,,,,,,,,"sku=api-simple-one-export-import,attribute=option1|sku=api-simple-two-export-import,attribute=option2",attribute=attributeExportImport diff --git a/dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv b/dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv new file mode 100644 index 0000000000000..fc27af79fc162 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv @@ -0,0 +1,2 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,associated_skus,configurable_variations,configurable_variation_labels +api-configurable-export-import-product,,Default,configurable,Default Category/CategoryExportImport,base,API Configurable Export Import Product,,,2,1,Taxable Goods,"Catalog, Search",123,,,,api-configurable-export-import-product,,,,/m/a/magento-logo.png,Magento Logo,/m/a/magento-logo.png,Magento Logo,/m/a/magento-logo.png,Magento Logo,,,"7/26/19, 8:21 AM","7/26/19, 8:21 AM",,,Block after Info Column,,,,,,,,,,,Use config,,,0,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,0,,,,,,,,,,,,,,,,,,"sku=api-simple-one-export-import,attribute=option1|sku=api-simple-two-export-import,attribute=option2",attribute=attributeExportImport From e4068ad05ece76026b137cf96bb3273b179eedd9 Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Thu, 12 Sep 2019 12:46:34 +0300 Subject: [PATCH 763/841] MC-6443: Product custom URL Key is preserved when assigned to a Category (without custom URL Key) alongside with another Product without custom URL Key --- .../Mftf/ActionGroup/AdminCategoryActionGroup.xml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 96575ee475a27..dc89d2fa5c045 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -405,13 +405,10 @@ <argument name="productSku" type="string"/> </arguments> - <conditionalClick selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" dependentSelector="{{CatalogProductsSection.resetFilter}}" visible="false" stepKey="clickOnProductInCategory"/> - <waitForPageLoad time="30" stepKey="waitForProductsInCategoryOpened"/> - <click selector="{{CatalogProductsSection.resetFilter}}" stepKey="clickOnResetFilter"/> - <waitForPageLoad time="30" stepKey="waitForProductsToLoad"/> - <fillField selector="{{AdminCategoryContentSection.productTableColumnSku}}" userInput="{{productSku}}" stepKey="selectProduct"/> - <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> - <waitForPageLoad time="30" stepKey="waitForSearch"/> + <conditionalClick selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="false" stepKey="clickOnProductInCategory"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickOnResetFilter"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnSku}}" userInput="{{productSku}}" stepKey="fillSkuFilter"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton"/> <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> </actionGroup> </actionGroups> From 012d1aa865ab75fb961e43220e4b9cad8b5796fc Mon Sep 17 00:00:00 2001 From: Viktor Petryk <victor.petryk@transoftgroup.com> Date: Thu, 12 Sep 2019 13:38:22 +0300 Subject: [PATCH 764/841] MC-6358: Import products with error entries --- .../AdminImportProductsActionGroup.xml | 29 +++++++-- .../Mftf/Section/AdminImportMainSection.xml | 2 + .../AdminImportValidationMessagesSection.xml | 17 +++++ .../AdminCheckDoubleImportOfProductsTest.xml | 4 +- ...mportProductsWithAddUpdateBehaviorTest.xml | 4 +- ...dminImportProductsWithErrorEntriesTest.xml | 65 +++++++++++++++++++ ...nImportProductsWithReplaceBehaviorTest.xml | 2 +- .../tests/_data/catalog_product_err_img.csv | 11 ++++ 8 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportValidationMessagesSection.xml create mode 100644 app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml create mode 100644 dev/tests/acceptance/tests/_data/catalog_product_err_img.csv diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml index 2ac790b953ec1..3574e6612162d 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml @@ -14,21 +14,36 @@ </annotations> <arguments> <argument name="behavior" type="string"/> + <argument name="validationStrategy" type="string" defaultValue="Stop on Error"/> + <argument name="allowedErrorsCount" type="string" defaultValue="10"/> <argument name="importFile" type="string"/> - <argument name="importMessage" type="string"/> + <argument name="importNoticeMessage" type="string"/> + <argument name="importMessageType" type="string" defaultValue="success"/> + <argument name="importMessage" type="string" defaultValue="Import successfully done"/> </arguments> <amOnPage url="{{AdminImportIndexPage.url}}" stepKey="goToImportIndexPage"/> - <waitForPageLoad stepKey="AdminImportMainSectionLoad"/> + <waitForPageLoad stepKey="adminImportMainSectionLoad"/> <selectOption selector="{{AdminImportMainSection.entityType}}" userInput="Products" stepKey="selectProductsOption"/> <waitForElementVisible selector="{{AdminImportMainSection.importBehavior}}" stepKey="waitForImportBehaviorElementVisible"/> - <selectOption selector="{{AdminImportMainSection.importBehavior}}" userInput="{{behavior}}" stepKey="selectImportOption"/> + <selectOption selector="{{AdminImportMainSection.importBehavior}}" userInput="{{behavior}}" stepKey="selectImportBehaviorOption"/> + <selectOption selector="{{AdminImportMainSection.validationStrategy}}" userInput="{{validationStrategy}}" stepKey="selectValidationStrategyOption"/> + <fillField selector="{{AdminImportMainSection.allowedErrorsCount}}" userInput="{{allowedErrorsCount}}" stepKey="fillAllowedErrorsCountField"/> <attachFile selector="{{AdminImportMainSection.selectFileToImport}}" userInput="{{importFile}}" stepKey="attachFileForImport"/> <click selector="{{AdminImportHeaderSection.checkDataButton}}" stepKey="clickCheckDataButton"/> <click selector="{{AdminImportMainSection.importButton}}" stepKey="clickImportButton"/> - <waitForPageLoad stepKey="AdminImportMainSectionLoad2"/> - <see selector="{{AdminMessagesSection.successMessage}}" userInput="Import successfully done" stepKey="assertSuccessMessage"/> - <waitForPageLoad stepKey="AdminMessagesSection"/> - <see selector="{{AdminMessagesSection.notice}}" userInput="{{importMessage}}" stepKey="seeImportMessage"/> + <waitForElementVisible selector="{{AdminImportValidationMessagesSection.notice}}" stepKey="waitForNoticeMessage"/> + <see selector="{{AdminImportValidationMessagesSection.notice}}" userInput="{{importNoticeMessage}}" stepKey="seeNoticeMessage"/> + <see selector="{{AdminImportValidationMessagesSection.messageByType(importMessageType)}}" userInput="{{importMessage}}" stepKey="seeImportMessage"/> + </actionGroup> + + <actionGroup name="AdminImportProductsWithCheckDataActionGroup" extends="AdminImportProductsActionGroup"> + <arguments> + <argument name="validationNoticeMessage" type="string"/> + <argument name="validationMessage" type="string" defaultValue="File is valid! To start import process press "Import" button"/> + </arguments> + <waitForElementVisible selector="{{AdminImportValidationMessagesSection.notice}}" after="clickCheckDataButton" stepKey="waitForValidationNoticeMessage"/> + <see selector="{{AdminImportValidationMessagesSection.notice}}" userInput="{{validationNoticeMessage}}" after="waitForValidationNoticeMessage" stepKey="seeValidationNoticeMessage"/> + <see selector="{{AdminImportValidationMessagesSection.success}}" userInput="{{validationMessage}}" after="seeValidationNoticeMessage" stepKey="seeValidationMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml index 2ce6b1e35777f..d44b93bf05c94 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml @@ -13,5 +13,7 @@ <element name="importBehavior" type="select" selector="#basic_behavior"/> <element name="selectFileToImport" type="input" selector="#import_file"/> <element name="importButton" type="button" selector="#import_validation_container button" timeout="30"/> + <element name="validationStrategy" type="select" selector="#basic_behaviorvalidation_strategy"/> + <element name="allowedErrorsCount" type="input" selector="#basic_behavior_allowed_error_count"/> </section> </sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportValidationMessagesSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportValidationMessagesSection.xml new file mode 100644 index 0000000000000..370d9546fa2f7 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportValidationMessagesSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminImportValidationMessagesSection"> + <element name="notice" type="text" selector="#import_validation_messages .message-notice"/> + <element name="success" type="text" selector="#import_validation_messages .message-success"/> + <element name="messageByType" type="text" selector="#import_validation_messages .message-{{messageType}}" parameterized="true" /> + <element name="importErrorList" type="text" selector="#import_validation_messages .import-error-list"/> + </section> +</sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckDoubleImportOfProductsTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckDoubleImportOfProductsTest.xml index 0f2dde99b9016..909c6101fe53e 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckDoubleImportOfProductsTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminCheckDoubleImportOfProductsTest.xml @@ -60,14 +60,14 @@ <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProductsFirstTime"> <argument name="behavior" value="Add/Update"/> <argument name="importFile" value="prepared-for-sample-data.csv"/> - <argument name="importMessage" value="Created: 100, Updated: 3, Deleted: 0"/> + <argument name="importNoticeMessage" value="Created: 100, Updated: 3, Deleted: 0"/> </actionGroup> <!-- Import products with add/update behavior again --> <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProductsSecondTime"> <argument name="behavior" value="Add/Update"/> <argument name="importFile" value="prepared-for-sample-data.csv"/> - <argument name="importMessage" value="Created: 0, Updated: 300, Deleted: 0"/> + <argument name="importNoticeMessage" value="Created: 0, Updated: 300, Deleted: 0"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml index ceb4e93e4e9aa..796732d572290 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml @@ -72,7 +72,7 @@ <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProducts"> <argument name="behavior" value="Add/Update"/> <argument name="importFile" value="catalog_import_products.csv"/> - <argument name="importMessage" value="Created: 2, Updated: 1, Deleted: 0"/> + <argument name="importNoticeMessage" value="Created: 2, Updated: 1, Deleted: 0"/> </actionGroup> <!-- Assert Simple Product1 on grid--> @@ -109,7 +109,7 @@ <actionGroup ref="StoreFrontProductValidationActionGroup" stepKey="storeFrontSimpleProduct1Validation"> <argument name="product" value="SimpleProductAfterImport1"/> </actionGroup> - + <!-- Assert SimpleProduct2 on store front--> <actionGroup ref="StoreFrontProductValidationActionGroup" stepKey="storeFrontSimpleProduct2Validation"> <argument name="product" value="SimpleProductAfterImport2"/> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml new file mode 100644 index 0000000000000..3ccd3b16842d9 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminImportProductsWithErrorEntriesTest"> + <annotations> + <features value="ImportExport"/> + <stories value="Import Products"/> + <title value="Import products with error entries"/> + <description value="Verify import status during import products with error entries"/> + <severity value="MAJOR"/> + <testCaseId value="MC-6358"/> + <useCaseId value="MAGETWO-65066"/> + <group value="importExport"/> + </annotations> + <before> + <!--Login to Admin Page--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!--Clear products grid filters--> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductsGridFilters"/> + <!--Delete all imported products--> + <actionGroup ref="deleteProductsIfTheyExist" stepKey="deleteAllProducts"/> + <!--Logout from Admin page--> + <actionGroup ref="logout" stepKey="logoutFromAdminPage"/> + </after> + + <!--Import products with "Skip error entries"--> + <actionGroup ref="AdminImportProductsWithCheckDataActionGroup" stepKey="importProductsWithSkipErrorEntries"> + <argument name="behavior" value="Add/Update"/> + <argument name="validationStrategy" value="Skip error entries"/> + <argument name="importFile" value="catalog_product_err_img.csv"/> + <argument name="importNoticeMessage" value="Created: 10, Updated: 0, Deleted: 0"/> + <argument name="validationNoticeMessage" value="Checked rows: 10, checked entities: 10, invalid rows: 0, total errors: 0"/> + </actionGroup> + <see selector="{{AdminImportValidationMessagesSection.importErrorList}}" userInput="row(s): 1, 2, 3, 4, 5, 6, 7, 8, 9, 10" stepKey="seeTenImportError"/> + + <!--Import products with "Stop on Error" and "Allowed Errors Count" equals 5--> + <actionGroup ref="AdminImportProductsWithCheckDataActionGroup" stepKey="importProductsWithAllowedErrorsCountFive"> + <argument name="behavior" value="Add/Update"/> + <argument name="allowedErrorsCount" value="5"/> + <argument name="importFile" value="catalog_product_err_img.csv"/> + <argument name="importNoticeMessage" value="Following Error(s) has been occurred during importing process"/> + <argument name="importMessageType" value="error"/> + <argument name="importMessage" value="Maximum error count has been reached or system error is occurred!"/> + <argument name="validationNoticeMessage" value="Checked rows: 10, checked entities: 10, invalid rows: 0, total errors: 0"/> + </actionGroup> + <see selector="{{AdminImportValidationMessagesSection.importErrorList}}" userInput="row(s): 1, 2, 3, 4, 5, 6" stepKey="seeAboutFiveImportError"/> + + <!--Import products with "Stop on Error" and "Allowed Errors Count" equals 11--> + <actionGroup ref="AdminImportProductsWithCheckDataActionGroup" stepKey="importProductsWithAllowedErrorsCountEleven"> + <argument name="behavior" value="Add/Update"/> + <argument name="allowedErrorsCount" value="11"/> + <argument name="importFile" value="catalog_product_err_img.csv"/> + <argument name="importNoticeMessage" value="Created: 0, Updated: 10, Deleted: 0"/> + <argument name="validationNoticeMessage" value="Checked rows: 10, checked entities: 10, invalid rows: 0, total errors: 0"/> + </actionGroup> + <see selector="{{AdminImportValidationMessagesSection.importErrorList}}" userInput="row(s): 1, 2, 3, 4, 5, 6, 7, 8, 9, 10" stepKey="seeAboutTenImportError"/> + </test> +</tests> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml index d63a5546716b1..dc4ede1978de3 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml @@ -39,7 +39,7 @@ <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProducts"> <argument name="behavior" value="Replace"/> <argument name="importFile" value="catalog_import_products.csv"/> - <argument name="importMessage" value="Created: 3, Updated: 0, Deleted: 3"/> + <argument name="importNoticeMessage" value="Created: 3, Updated: 0, Deleted: 3"/> </actionGroup> </test> </tests> diff --git a/dev/tests/acceptance/tests/_data/catalog_product_err_img.csv b/dev/tests/acceptance/tests/_data/catalog_product_err_img.csv new file mode 100644 index 0000000000000..97ac55e8e5a20 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/catalog_product_err_img.csv @@ -0,0 +1,11 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,additional_images +simple1,,Default,simple,,base,simple1,,,,1,Taxable Goods,"Catalog, Search",100,,,,simple1,test.jpg +simple2,,Default,simple,,base,simple2,,,,2,Taxable Goods,"Catalog, Search",101,,,,simple2,test.jpg +simple3,,Default,simple,,base,simple3,,,,3,Taxable Goods,"Catalog, Search",102,,,,simple3,test.jpg +simple4,,Default,simple,,base,simple4,,,,4,Taxable Goods,"Catalog, Search",103,,,,simple4,test.jpg +simple5,,Default,simple,,base,simple5,,,,5,Taxable Goods,"Catalog, Search",104,,,,simple5,test.jpg +simple6,,Default,simple,,base,simple6,,,,6,Taxable Goods,"Catalog, Search",105,,,,simple6,test.jpg +simple7,,Default,simple,,base,simple7,,,,7,Taxable Goods,"Catalog, Search",106,,,,simple7,test.jpg +simple8,,Default,simple,,base,simple8,,,,8,Taxable Goods,"Catalog, Search",107,,,,simple8,test.jpg +simple9,,Default,simple,,base,simple9,,,,9,Taxable Goods,"Catalog, Search",108,,,,simple9,test.jpg +simple10,,Default,simple,,base,simple10,,,,10,Taxable Goods,"Catalog, Search",109,,,,simple10,test.jpg From e779423e1a11fa930c43eed12ddf4833f2cd8cf7 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p.bystritsky@yandex.ru> Date: Thu, 12 Sep 2019 12:30:42 +0300 Subject: [PATCH 765/841] magento/magento2#24519: Web-Api test added. --- .../Customer/Api/AddressMetadataTest.php | 133 +++++++++++++++++- 1 file changed, 129 insertions(+), 4 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressMetadataTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressMetadataTest.php index 748947825ba09..fbf131bf4deca 100644 --- a/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressMetadataTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/AddressMetadataTest.php @@ -6,8 +6,11 @@ namespace Magento\Customer\Api; +use Magento\Config\Model\ResourceModel\Config; use Magento\Customer\Api\Data\AddressInterface as Address; use Magento\Customer\Model\Data\AttributeMetadata; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\WebapiAbstract; /** @@ -19,15 +22,40 @@ class AddressMetadataTest extends WebapiAbstract const SERVICE_VERSION = "V1"; const RESOURCE_PATH = "/V1/attributeMetadata/customerAddress"; + /** + * @var Config $config + */ + private $resourceConfig; + + /** + * @var ReinitableConfigInterface + */ + private $reinitConfig; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + + $objectManager = ObjectManager::getInstance(); + $this->resourceConfig = $objectManager->get(Config::class); + $this->reinitConfig = $objectManager->get(ReinitableConfigInterface::class); + } + /** * Test retrieval of attribute metadata for the address entity type. * * @param string $attributeCode The attribute code of the requested metadata. * @param array $expectedMetadata Expected entity metadata for the attribute code. * @dataProvider getAttributeMetadataDataProvider + * @magentoDbIsolation disabled */ - public function testGetAttributeMetadata($attributeCode, $expectedMetadata) + public function testGetAttributeMetadata($attributeCode, $configOptions, $expectedMetadata) { + $this->initConfig($configOptions); + $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . "/attribute/$attributeCode", @@ -54,12 +82,14 @@ public function testGetAttributeMetadata($attributeCode, $expectedMetadata) * Data provider for testGetAttributeMetadata. * * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function getAttributeMetadataDataProvider() { return [ Address::POSTCODE => [ Address::POSTCODE, + [], [ AttributeMetadata::FRONTEND_INPUT => 'text', AttributeMetadata::INPUT_FILTER => '', @@ -83,7 +113,85 @@ public function getAttributeMetadataDataProvider() AttributeMetadata::IS_SEARCHABLE_IN_GRID => true, AttributeMetadata::ATTRIBUTE_CODE => 'postcode', ], - ] + ], + 'prefix' => [ + 'prefix', + [ + ['path' => 'customer/address/prefix_show', 'value' => 'opt'], + ['path' => 'customer/address/prefix_options', 'value' => 'prefA;prefB'] + ], + [ + AttributeMetadata::FRONTEND_INPUT => 'text', + AttributeMetadata::INPUT_FILTER => '', + AttributeMetadata::STORE_LABEL => 'Name Prefix', + AttributeMetadata::MULTILINE_COUNT => 0, + AttributeMetadata::VALIDATION_RULES => [], + AttributeMetadata::VISIBLE => false, + AttributeMetadata::REQUIRED => false, + AttributeMetadata::DATA_MODEL => '', + AttributeMetadata::OPTIONS => [ + [ + 'label' => 'prefA', + 'value' => 'prefA', + ], + [ + 'label' => 'prefB', + 'value' => 'prefB', + ], + ], + AttributeMetadata::FRONTEND_CLASS => '', + AttributeMetadata::USER_DEFINED => false, + AttributeMetadata::SORT_ORDER => 10, + AttributeMetadata::FRONTEND_LABEL => 'Name Prefix', + AttributeMetadata::NOTE => '', + AttributeMetadata::SYSTEM => false, + AttributeMetadata::BACKEND_TYPE => 'static', + AttributeMetadata::IS_USED_IN_GRID => false, + AttributeMetadata::IS_VISIBLE_IN_GRID => false, + AttributeMetadata::IS_FILTERABLE_IN_GRID => false, + AttributeMetadata::IS_SEARCHABLE_IN_GRID => false, + AttributeMetadata::ATTRIBUTE_CODE => 'prefix', + ], + ], + 'suffix' => [ + 'suffix', + [ + ['path' => 'customer/address/suffix_show', 'value' => 'opt'], + ['path' => 'customer/address/suffix_options', 'value' => 'suffA;suffB'] + ], + [ + AttributeMetadata::FRONTEND_INPUT => 'text', + AttributeMetadata::INPUT_FILTER => '', + AttributeMetadata::STORE_LABEL => 'Name Suffix', + AttributeMetadata::MULTILINE_COUNT => 0, + AttributeMetadata::VALIDATION_RULES => [], + AttributeMetadata::VISIBLE => false, + AttributeMetadata::REQUIRED => false, + AttributeMetadata::DATA_MODEL => '', + AttributeMetadata::OPTIONS => [ + [ + 'label' => 'suffA', + 'value' => 'suffA', + ], + [ + 'label' => 'suffB', + 'value' => 'suffB', + ], + ], + AttributeMetadata::FRONTEND_CLASS => '', + AttributeMetadata::USER_DEFINED => false, + AttributeMetadata::SORT_ORDER => 50, + AttributeMetadata::FRONTEND_LABEL => 'Name Suffix', + AttributeMetadata::NOTE => '', + AttributeMetadata::SYSTEM => false, + AttributeMetadata::BACKEND_TYPE => 'static', + AttributeMetadata::IS_USED_IN_GRID => false, + AttributeMetadata::IS_VISIBLE_IN_GRID => false, + AttributeMetadata::IS_FILTERABLE_IN_GRID => false, + AttributeMetadata::IS_SEARCHABLE_IN_GRID => false, + AttributeMetadata::ATTRIBUTE_CODE => 'suffix', + ], + ], ]; } @@ -106,7 +214,7 @@ public function testGetAllAttributesMetadata() $attributeMetadata = $this->_webApiCall($serviceInfo); $this->assertCount(19, $attributeMetadata); - $postcode = $this->getAttributeMetadataDataProvider()[Address::POSTCODE][1]; + $postcode = $this->getAttributeMetadataDataProvider()[Address::POSTCODE][2]; $validationResult = $this->checkMultipleAttributesValidationRules($postcode, $attributeMetadata); list($postcode, $attributeMetadata) = $validationResult; $this->assertContains($postcode, $attributeMetadata); @@ -187,7 +295,7 @@ public function getAttributesDataProvider() return [ [ 'customer_address_edit', - $attributeMetadata[Address::POSTCODE][1], + $attributeMetadata[Address::POSTCODE][2], ] ]; } @@ -200,6 +308,7 @@ public function getAttributesDataProvider() * @param array $actualResult * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * phpcs:disable Generic.Metrics.NestingLevel */ public function checkValidationRules($expectedResult, $actualResult) { @@ -235,6 +344,7 @@ public function checkValidationRules($expectedResult, $actualResult) } return [$expectedResult, $actualResult]; } + //phpcs:enable /** * Check specific attribute validation rules in set of multiple attributes @@ -277,4 +387,19 @@ public static function tearDownAfterClass() $attribute->delete(); } } + + /** + * Set core config data. + * + * @param $configOptions + */ + private function initConfig(array $configOptions): void + { + if ($configOptions) { + foreach ($configOptions as $option) { + $this->resourceConfig->saveConfig($option['path'], $option['value']); + } + } + $this->reinitConfig->reinit(); + } } From 56f29a4e89590ed3dea83754dc3534f493a2ead7 Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Thu, 12 Sep 2019 14:47:28 +0300 Subject: [PATCH 766/841] MC-19737: Date type field saving incorrect value for a store in different timezone --- app/code/Magento/Catalog/Model/Product/Option/Type/Date.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php index 2b4739ebeb736..6ac48c565e842 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php @@ -159,7 +159,7 @@ public function prepareForCart() if ($this->_dateExists()) { if ($this->useCalendar()) { - $timestamp += $this->_localeDate->date($value['date'], null, true, false)->getTimestamp(); + $timestamp += $this->_localeDate->date($value['date'], null, false, false)->getTimestamp(); } else { $timestamp += mktime(0, 0, 0, $value['month'], $value['day'], $value['year']); } From 75dce6df5072ccf54ea8c65d6f34af8b7b2dc562 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Thu, 12 Sep 2019 16:29:32 +0300 Subject: [PATCH 767/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- .../Test/StorefrontAddConfigurableProductToShoppingCartTest.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml index 09608eef7178a..18e4cd7db2871 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml @@ -20,6 +20,8 @@ <before> <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRatePrice"/> + <magentoCLI command="config:set {{EnableFlatRateToAllAllowedCountriesConfigData.path}} {{EnableFlatRateToAllAllowedCountriesConfigData.value}}" stepKey="allowFlatRateToAllCountries"/> + <!-- Create Default Category --> <createData entity="_defaultCategory" stepKey="createCategory"/> From effb3ba4a2db65864b26bb0fb97b5943e9f12c95 Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Thu, 12 Sep 2019 17:03:17 +0300 Subject: [PATCH 768/841] MC-15845: Samples of Downloadable Products are not accessible, if product is disabled --- ...refrontOpenDownloadableLinkActionGroup.xml | 17 +++ ...frontOpenDownloadableSampleActionGroup.xml | 17 +++ .../Downloadable/Test/Mftf/Data/LinkData.xml | 8 +- .../downloadable_link_sample-meta.xml | 23 ++++ .../Page/StorefrontDownloadableLinkPage.xml | 13 ++ .../Page/StorefrontDownloadableSamplePage.xml | 13 ++ .../Test/Mftf/Page/StorefrontProductPage.xml | 14 +++ .../StorefrontDownloadableProductSection.xml | 2 + ...ableProductSamplesAreNotAccessibleTest.xml | 114 ++++++++++++++++++ 9 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableLinkActionGroup.xml create mode 100644 app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableSampleActionGroup.xml create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link_sample-meta.xml create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableLinkPage.xml create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableSamplePage.xml create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontProductPage.xml create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableLinkActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableLinkActionGroup.xml new file mode 100644 index 0000000000000..565439655138e --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableLinkActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenDownloadableLinkActionGroup"> + <arguments> + <argument name="linkId" type="string"/> + </arguments> + <amOnPage url="{{StorefrontDownloadableLinkPage.url(linkId)}}" stepKey="openDownloadableLink"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableSampleActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableSampleActionGroup.xml new file mode 100644 index 0000000000000..25ac45317fe42 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontOpenDownloadableSampleActionGroup.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontOpenDownloadableSampleActionGroup"> + <arguments> + <argument name="sampleId" type="string"/> + </arguments> + <amOnPage url="{{StorefrontDownloadableSamplePage.url(sampleId)}}" stepKey="openDownloadableSample"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml index 08f1c2349357d..eb3ad674a0fdf 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml @@ -63,6 +63,12 @@ <data key="file_type">URL</data> <data key="file">https://static.magento.com/sites/all/themes/mag_redesign/images/magento-logo.svg</data> </entity> + <entity name="DownloadableSample" type="downloadable_sample"> + <data key="title" unique="suffix">downloadableSampleUrl</data> + <data key="sort_order">1</data> + <data key="sample_type">url</data> + <data key="sample_url">http://example.com</data> + </entity> <entity name="ApiDownloadableLink" type="downloadable_link"> <data key="title" unique="suffix">Api Downloadable Link</data> <data key="price">2.00</data> @@ -72,4 +78,4 @@ <data key="sort_order">0</data> <data key="link_url">https://static.magento.com/sites/all/themes/mag_redesign/images/magento-logo.svg</data> </entity> -</entities> \ No newline at end of file +</entities> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link_sample-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link_sample-meta.xml new file mode 100644 index 0000000000000..b26bbb7af5a35 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link_sample-meta.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateDownloadableSample" dataType="downloadable_sample" type="create" auth="adminOauth" url="/V1/products/{sku}/downloadable-links/samples" method="POST"> + <contentType>application/json</contentType> + <object dataType="downloadable_sample" key="sample"> + <field key="title">string</field> + <field key="sort_order">integer</field> + <field key="sample_type">string</field> + <field key="sample_file">string</field> + <field key="sample_file_content">sample_file_content</field> + <field key="sample_url">string</field> + </object> + <field key="isGlobalScopeContent">boolean</field> + </operation> +</operations> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableLinkPage.xml b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableLinkPage.xml new file mode 100644 index 0000000000000..7ab6b211d7441 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableLinkPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontDownloadableLinkPage" url="downloadable/download/linkSample/link_id/{{id}}/" area="storefront" module="Magento_Downloadable" parameterized="true"> + </page> +</pages> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableSamplePage.xml b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableSamplePage.xml new file mode 100644 index 0000000000000..0d588faa777c0 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontDownloadableSamplePage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontDownloadableSamplePage" url="downloadable/download/sample/sample_id/{{id}}/" area="storefront" module="Magento_Downloadable" parameterized="true"> + </page> +</pages> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontProductPage.xml b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontProductPage.xml new file mode 100644 index 0000000000000..7b9d205d19dc5 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Page/StorefrontProductPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontProductPage" url="/{{var1}}.html" area="storefront" module="Magento_Catalog" parameterized="true"> + <section name="StorefrontDownloadableProductSection" /> + </page> +</pages> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml index a1db2d4d94941..543aea7d8297f 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontDownloadableProductSection.xml @@ -12,5 +12,7 @@ <element name="downloadableLinkBlock" type="text" selector="//div[contains(@class, 'field downloads required')]//span[text()='Downloadable Links']"/> <element name="downloadableLinkLabel" type="text" selector="//label[contains(., '{{title}}')]" parameterized="true" timeout="30"/> <element name="downloadableLinkByTitle" type="input" selector="//*[@id='downloadable-links-list']/*[contains(.,'{{title}}')]//input" parameterized="true" timeout="30"/> + <element name="downloadableLinkSampleByTitle" type="text" selector="//label[contains(., '{{title}}')]/a[contains(@class, 'sample link')]" parameterized="true"/> + <element name="downloadableSampleLabel" type="text" selector="//a[contains(.,normalize-space('{{title}}'))]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml new file mode 100644 index 0000000000000..fc8b28ac05530 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml @@ -0,0 +1,114 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="VerifyDisableDownloadableProductSamplesAreNotAccessibleTest"> + <annotations> + <features value="Downloadable"/> + <stories value="Downloadable product"/> + <title value="Samples of Downloadable Products are not accessible, if product is disabled"/> + <description value="Samples of Downloadable Products are not accessible, if product is disabled"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-15845"/> + <useCaseId value="MC-14824"/> + <group value="downloadable"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + + <!-- Create downloadable product --> + <createData entity="DownloadableProductWithOneLink" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Add downloadable link --> + <createData entity="downloadableLink1" stepKey="addDownloadableLink"> + <requiredEntity createDataKey="createProduct"/> + </createData> + + <!-- Add downloadable sample --> + <createData entity="DownloadableSample" stepKey="addDownloadableSample"> + <requiredEntity createDataKey="createProduct"/> + </createData> + </before> + <after> + <!-- Delete product --> + <deleteData createDataKey="createProduct" stepKey="deleteDownloadableProduct"/> + + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Admin logout --> + <actionGroup ref="logout" stepKey="adminLogout"/> + </after> + + <!-- Open Downloadable product from precondition on Storefront --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openStorefrontProductPage"> + <argument name="productUrl" value="$createProduct.custom_attributes[url_key]$"/> + </actionGroup> + + <!-- Sample url is accessible --> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeDownloadableSample"> + <argument name="selector" value="{{StorefrontDownloadableProductSection.downloadableSampleLabel(DownloadableSample.title)}}"/> + </actionGroup> + <click selector="{{StorefrontDownloadableProductSection.downloadableSampleLabel(DownloadableSample.title)}}" stepKey="clickDownloadableSample"/> + + <!-- Grab Sample id --> + <switchToNextTab stepKey="switchToSampleTab"/> + <grabFromCurrentUrl regex="~/sample_id/(\d+)/~" stepKey="grabDownloadableSampleId"/> + <closeTab stepKey="closeSampleTab"/> + + <!-- Link Sample url is accessible --> + <actionGroup ref="AssertStorefrontSeeElementActionGroup" stepKey="seeDownloadableLink"> + <argument name="selector" value="{{StorefrontDownloadableProductSection.downloadableLinkLabel(downloadableLink1.title)}}"/> + </actionGroup> + <click selector="{{StorefrontDownloadableProductSection.downloadableLinkSampleByTitle(downloadableLink1.title)}}" stepKey="clickDownloadableLinkSample"/> + + <!-- Grab Link Sample id --> + <switchToNextTab stepKey="switchToLinkSampleTab"/> + <grabFromCurrentUrl regex="~/link_id/(\d+)/~" stepKey="grabDownloadableLinkId"/> + <closeTab stepKey="closeLinkSampleTab"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Open Downloadable product from precondition --> + <actionGroup ref="goToProductPageViaID" stepKey="openProductEditPage"> + <argument name="productId" value="$createProduct.id$"/> + </actionGroup> + + <!-- Change status of product to "Disable" and save it --> + <actionGroup ref="AdminSetProductDisabled" stepKey="disableProduct"/> + <actionGroup ref="saveProductForm" stepKey="clickSaveProduct"/> + + <!-- Assert product is disable on Storefront --> + <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="openProductPage"> + <argument name="category" value="$createCategory$"/> + </actionGroup> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + + <!-- Navigate to Link Sample url on Storefront --> + <actionGroup ref="StorefrontOpenDownloadableLinkActionGroup" stepKey="openDownloadableLinkSample"> + <argument name="linkId" value="{$grabDownloadableLinkId}"/> + </actionGroup> + + <!-- Link Sample url is not accessible. You are redirected to Home Page --> + <seeInCurrentUrl url="{{StorefrontHomePage.url}}" stepKey="seeRedirectToHomePage"/> + + <!-- Navigate to Sample url on Storefront --> + <actionGroup ref="StorefrontOpenDownloadableSampleActionGroup" stepKey="openDownloadableSample"> + <argument name="sampleId" value="{$grabDownloadableSampleId}"/> + </actionGroup> + + <!-- Sample url is not accessible. You are redirected to Home Page --> + <seeInCurrentUrl url="{{StorefrontHomePage.url}}" stepKey="seeHomePage"/> + </test> +</tests> From 248c682adbaf707fe89694214c7037cd6d8abf4a Mon Sep 17 00:00:00 2001 From: Viktor Petryk <victor.petryk@transoftgroup.com> Date: Thu, 12 Sep 2019 18:11:32 +0300 Subject: [PATCH 769/841] MC-6358: Import products with error entries --- .../Mftf/ActionGroup/AdminImportProductsActionGroup.xml | 2 +- .../ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml | 1 + .../Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml index 3574e6612162d..6bcca6c86c98c 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml @@ -37,7 +37,7 @@ <see selector="{{AdminImportValidationMessagesSection.messageByType(importMessageType)}}" userInput="{{importMessage}}" stepKey="seeImportMessage"/> </actionGroup> - <actionGroup name="AdminImportProductsWithCheckDataActionGroup" extends="AdminImportProductsActionGroup"> + <actionGroup name="AdminImportProductsWithCheckValidationResultActionGroup" extends="AdminImportProductsActionGroup"> <arguments> <argument name="validationNoticeMessage" type="string"/> <argument name="validationMessage" type="string" defaultValue="File is valid! To start import process press "Import" button"/> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml index 87807eb9b0e85..6262931179599 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml @@ -11,5 +11,6 @@ <page name="AdminImportIndexPage" url="admin/import/" area="admin" module="Magento_ImportExport"> <section name="AdminImportHeaderSection"/> <section name="AdminImportMainSection"/> + <section name="AdminImportValidationMessagesSection"/> </page> </pages> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml index 3ccd3b16842d9..94840a4ea6142 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithErrorEntriesTest.xml @@ -31,7 +31,7 @@ </after> <!--Import products with "Skip error entries"--> - <actionGroup ref="AdminImportProductsWithCheckDataActionGroup" stepKey="importProductsWithSkipErrorEntries"> + <actionGroup ref="AdminImportProductsWithCheckValidationResultActionGroup" stepKey="importProductsWithSkipErrorEntries"> <argument name="behavior" value="Add/Update"/> <argument name="validationStrategy" value="Skip error entries"/> <argument name="importFile" value="catalog_product_err_img.csv"/> @@ -41,7 +41,7 @@ <see selector="{{AdminImportValidationMessagesSection.importErrorList}}" userInput="row(s): 1, 2, 3, 4, 5, 6, 7, 8, 9, 10" stepKey="seeTenImportError"/> <!--Import products with "Stop on Error" and "Allowed Errors Count" equals 5--> - <actionGroup ref="AdminImportProductsWithCheckDataActionGroup" stepKey="importProductsWithAllowedErrorsCountFive"> + <actionGroup ref="AdminImportProductsWithCheckValidationResultActionGroup" stepKey="importProductsWithAllowedErrorsCountFive"> <argument name="behavior" value="Add/Update"/> <argument name="allowedErrorsCount" value="5"/> <argument name="importFile" value="catalog_product_err_img.csv"/> @@ -53,7 +53,7 @@ <see selector="{{AdminImportValidationMessagesSection.importErrorList}}" userInput="row(s): 1, 2, 3, 4, 5, 6" stepKey="seeAboutFiveImportError"/> <!--Import products with "Stop on Error" and "Allowed Errors Count" equals 11--> - <actionGroup ref="AdminImportProductsWithCheckDataActionGroup" stepKey="importProductsWithAllowedErrorsCountEleven"> + <actionGroup ref="AdminImportProductsWithCheckValidationResultActionGroup" stepKey="importProductsWithAllowedErrorsCountEleven"> <argument name="behavior" value="Add/Update"/> <argument name="allowedErrorsCount" value="11"/> <argument name="importFile" value="catalog_product_err_img.csv"/> From 7fa0efbade86e9b1d38c97bffd80d1470a052df3 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Thu, 12 Sep 2019 11:24:37 -0500 Subject: [PATCH 770/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - review comments --- .../GraphQl/Catalog/ProductSearchTest.php | 70 +++++++------------ .../_files/products_for_relevance_sorting.php | 8 +-- 2 files changed, 31 insertions(+), 47 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index f3dbff8be9d24..8b70a31f04588 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -14,9 +14,6 @@ use Magento\Catalog\Model\Category; use Magento\Catalog\Model\CategoryLinkManagement; use Magento\Eav\Model\Config; -use Magento\Framework\Config\Data; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Indexer\IndexerInterface; use Magento\Indexer\Model\Indexer; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; @@ -115,10 +112,7 @@ public function testLayeredNavigationForConfigurableProducts() $firstOption = $options[0]->getValue(); $secondOption = $options[1]->getValue(); $query = $this->getQueryProductsWithArrayOfCustomAttributes($attributeCode, $firstOption, $secondOption); - $objectManager = Bootstrap::getObjectManager(); - $indexer = $objectManager->create(Indexer::class); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); + $this->reIndexAndCleanCache(); $response = $this->graphQlQuery($query); $this->assertEquals(2, $response['products']['total_count']); @@ -262,23 +256,22 @@ public function testFilterProductsByDropDownCustomAttribute() } QUERY; + $objectManager = Bootstrap::getObjectManager(); /** @var ProductRepositoryInterface $productRepository */ - $productRepository = ObjectManager::getInstance()->get(ProductRepositoryInterface::class); + $productRepository = $objectManager->get(ProductRepositoryInterface::class); $product1 = $productRepository->get('simple'); $product2 = $productRepository->get('12345'); $product3 = $productRepository->get('simple-4'); $filteredProducts = [$product1, $product2, $product3 ]; - $objectManager = Bootstrap::getObjectManager(); - $indexer = $objectManager->create(Indexer::class); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); + $countOfFilteredProducts = count($filteredProducts); + $this->reIndexAndCleanCache(); $response = $this->graphQlQuery($query); $this->assertEquals(3, $response['products']['total_count'], 'Number of products returned is incorrect'); $this->assertTrue(count($response['products']['filters']) > 0, 'Product filters is not empty'); $this->assertCount(3, $response['products']['aggregations'], 'Incorrect count of aggregations'); + $productItemsInResponse = array_map(null, $response['products']['items'], $filteredProducts); - //phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall - for ($itemIndex = 0; $itemIndex < count($filteredProducts); $itemIndex++) { + for ($itemIndex = 0; $itemIndex < $countOfFilteredProducts; $itemIndex++) { $this->assertNotEmpty($productItemsInResponse[$itemIndex]); //validate that correct products are returned $this->assertResponseFields( @@ -290,7 +283,7 @@ public function testFilterProductsByDropDownCustomAttribute() } /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $eavConfig = $objectManager->get(Config::class); $attribute = $eavConfig->getAttribute('catalog_product', 'second_test_configurable'); // Validate custom attribute filter layer data from aggregations $this->assertResponseFields( @@ -309,6 +302,14 @@ public function testFilterProductsByDropDownCustomAttribute() ] ); } + private function reIndexAndCleanCache() + { + $objectManager = Bootstrap::getObjectManager(); + $indexer = $objectManager->create(Indexer::class); + $indexer->load('catalogsearch_fulltext'); + $indexer->reindexAll(); + CacheCleaner::cleanAll(); + } /** * Filter products using an array of multi select custom attributes * @@ -318,20 +319,17 @@ public function testFilterProductsByDropDownCustomAttribute() public function testFilterProductsByMultiSelectCustomAttributes() { $objectManager = Bootstrap::getObjectManager(); - $indexer = $objectManager->create(Indexer::class); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); - CacheCleaner::cleanAll(); + $this->reIndexAndCleanCache(); $attributeCode = 'multiselect_attribute'; /** @var \Magento\Eav\Model\Config $eavConfig */ - $eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); + $eavConfig = $objectManager->get(\Magento\Eav\Model\Config::class); $attribute = $eavConfig->getAttribute('catalog_product', $attributeCode); /** @var AttributeOptionInterface[] $options */ $options = $attribute->getOptions(); array_shift($options); + $countOptions = count($options); $optionValues = []; - // phpcs:ignore Generic.CodeAnalysis.ForLoopWithTestFunctionCall - for ($i = 0; $i < count($options); $i++) { + for ($i = 0; $i < $countOptions; $i++) { $optionValues[] = $options[$i]->getValue(); } $query = <<<QUERY @@ -413,11 +411,7 @@ private function getDefaultAttributeOptionValue(string $attributeCode) : string */ public function testSearchAndFilterByCustomAttribute() { - $objectManager = Bootstrap::getObjectManager(); - $indexer = $objectManager->create(Indexer::class); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); - CacheCleaner::cleanAll(); + $this->reIndexAndCleanCache(); $attribute_code = 'second_test_configurable'; $optionValue = $this->getDefaultAttributeOptionValue($attribute_code); @@ -564,12 +558,7 @@ public function testSearchAndFilterByCustomAttribute() */ public function testFilterByCategoryIdAndCustomAttribute() { - $objectManager = Bootstrap::getObjectManager(); - $indexer = $objectManager->create(Indexer::class); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); - CacheCleaner::cleanAll(); - + $this->reIndexAndCleanCache(); $categoryId = 13; $optionValue = $this->getDefaultAttributeOptionValue('second_test_configurable'); $query = <<<QUERY @@ -765,7 +754,8 @@ public function testFilterBySingleProductUrlKey() $response = $this->graphQlQuery($query); $this->assertEquals(1, $response['products']['total_count'], 'More than 1 product found'); $this->assertCount(2, $response['products']['aggregations']); - $this->assertResponseFields($response['products']['items'][0], + $this->assertResponseFields( + $response['products']['items'][0], [ 'name' => $product->getName(), 'sku' => $product->getSku(), @@ -1440,17 +1430,14 @@ public function testFilterProductsBySingleCategoryId() */ public function testSearchAndSortByRelevance() { - $objectManager = Bootstrap::getObjectManager(); - $indexer = $objectManager->create(Indexer::class); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); + $this->reIndexAndCleanCache(); $search_term ="blue"; $query = <<<QUERY { products( search:"{$search_term}" - + sort:{relevance:DESC} pageSize: 5 currentPage: 1 ) @@ -1571,10 +1558,7 @@ public function testFilterByExactSkuAndSortByPriceDesc() */ public function testProductBasicFullTextSearchQuery() { - $objectManager = Bootstrap::getObjectManager(); - $indexer = $objectManager->create(Indexer::class); - $indexer->load('catalogsearch_fulltext'); - $indexer->reindexAll(); + $this->reIndexAndCleanCache(); $textToSearch = 'blue'; $query =<<<QUERY diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php index eb1a42635d37b..9e84178dc4c89 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php @@ -65,10 +65,10 @@ ->setSku('navy-striped-shoes') ->setPrice(40) ->setWeight(8) - ->setDescription('Red white and blue flip flops at <b>one</b>') - ->setMetaTitle('navy colored shoes meta title') - ->setMetaKeyword('navy, striped , women, kids') - ->setMetaDescription('shoes women kids meta description') + ->setDescription('Blue striped flip flops at <b>one</b>') + ->setMetaTitle('navy blue colored shoes meta title') + ->setMetaKeyword('blue, navy, striped , women, kids') + ->setMetaDescription('blue', 'shoes women kids meta description') ->setStockData(['use_config_manage_stock' => 0]) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) From 6ff0c12a2d3804fff46e9e68336cc5443d58a142 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Thu, 12 Sep 2019 13:03:40 -0500 Subject: [PATCH 771/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - fix schema descriptions --- app/code/Magento/GraphQl/etc/schema.graphqls | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 90a4bd7376e45..77bba5ea3a9d4 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -65,18 +65,18 @@ input FilterTypeInput @doc(description: "FilterTypeInput specifies which action nin: [String] @doc(description: "Not in. The value can contain a set of comma-separated values") } -input FilterEqualTypeInput @doc(description: "Specifies which action will be performed in a query ") { - in: [String] @doc(description: "In. The value can contain a set of comma-separated values") - eq: String @doc(description: "Equals") +input FilterEqualTypeInput @doc(description: "Defines a filter that matches the input exactly.") { + in: [String] @doc(description: "An array of values to filter on") + eq: String @doc(description: "A string to filter on") } -input FilterRangeTypeInput @doc(description: "Specifies which action will be performed in a query ") { - from: String @doc(description: "From") - to: String @doc(description: "To") +input FilterRangeTypeInput @doc(description: "Defines a filter that matches a range of values, such as prices or dates.") { + from: String @doc(description: "The beginning of the range") + to: String @doc(description: "The end of the range") } -input FilterMatchTypeInput @doc(description: "Specifies which action will be performed in a query ") { - match: String @doc(description: "Match. Can be used for fuzzy matching.") +input FilterMatchTypeInput @doc(description: "Defines a filter that performs a fuzzy search.") { + match: String @doc(description: "One or more words to filter on") } type SearchResultPageInfo @doc(description: "SearchResultPageInfo provides navigation for the query response") { From c78dfec370f4ad98f9e9cd0a654947132f8c4321 Mon Sep 17 00:00:00 2001 From: Oleksandr Iegorov <oiegorov@magento.com> Date: Thu, 12 Sep 2019 13:25:00 -0500 Subject: [PATCH 772/841] MC-19791: Poor performance on sales order update - string to integer --- .../Magento/Framework/Model/ResourceModel/Db/AbstractDb.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php index 90fa73de427bc..ca21c85af8d9a 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/AbstractDb.php @@ -785,7 +785,7 @@ protected function saveNewObject(\Magento\Framework\Model\AbstractModel $object) } /** - * Check is column data type is numeric + * Check if column data type is numeric * * Based on column description * From 8afbd8c97725b585f81a51764ac7e6cf43093d8d Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko <ymiroshnychenko@magento.com> Date: Thu, 12 Sep 2019 13:49:38 -0500 Subject: [PATCH 773/841] MC-19421: Reduce q-ty of Reports Created in /app/*/var/report --- pub/errors/processor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pub/errors/processor.php b/pub/errors/processor.php index 63d054319fb07..108ddf4e03cbb 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -629,7 +629,7 @@ private function getReportDirNestingLevel(string $reportId): int */ private function getMaxReportDirNestingLevel(string $reportId): int { - return (integer)floor(mb_strlen($reportId) / self::NUMBER_SYMBOLS_IN_SUBDIR_NAME); + return (int)floor(strlen($reportId) / self::NUMBER_SYMBOLS_IN_SUBDIR_NAME); } /** From 2b181d21bbc35cfb9514c3e592c1dbe116589b3b Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Thu, 12 Sep 2019 14:45:46 -0500 Subject: [PATCH 774/841] MC-18450: Allow custom attributes to be filterable and also returned in the layered navigation the product filter section in GraphQL - generate benchmark --- setup/performance-toolkit/benchmark.jmx | 60 ++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index a4128576e8e15..40983a097c58d 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -38447,7 +38447,7 @@ vars.put("configurable_sku", "Configurable Product - ${__time(YMD)}-${__threadNu <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${simple_product_sku}\" } },sort: {name: ASC})\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38523,7 +38523,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: {eq:\"${configurable_product_sku}\"} }, sort: {name: ASC}) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38592,7 +38592,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"configurable\"\n filter: {name: {match: \"Configurable Product\"} }\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"configurable\"\n filter: {name: {match: \"Configurable Product\"} }\n sort: {name: ASC}\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38660,7 +38660,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:${graphql_search_products_query_total_pages_fulltext_filter}\n search: \"configurable\"\n filter: {name: {match: \"Configurable Product\"} }\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:${graphql_search_products_query_total_pages_fulltext_filter}\n search: \"configurable\"\n filter: {name: {match: \"Configurable Product\"} }\n sort: {name: ASC}\n ) {\n total_count\n page_info {\n current_page\n page_size\n total_pages\n }\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38720,7 +38720,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"configurable\") {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"configurable\"\n sort: {name: ASC}) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t\t}\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38780,7 +38780,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"Option 1\") {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"Option 1\"\n sort: {name: ASC}) {\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n items_count\n ... on SwatchLayerFilterItemInterface {\n swatch_data {\n type\n value\n }\n }\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38840,7 +38840,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"Option 1\") {\n aggregations{\n attribute_code\n count\n label\n options{\n count\n label\n value\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(\n pageSize:20\n currentPage:1\n search: \"Option 1\"\n sort: {name: ASC}) {\n aggregations{\n attribute_code\n count\n label\n options{\n count\n label\n value\n }\n }\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n weight\n }\n ... on ConfigurableProduct {\n configurable_options {\n id\n attribute_id\n label\n position\n use_default\n attribute_code\n values {\n value_index\n label\n store_label\n default_label\n use_default_value\n }\n product_id\n }\n variants {\n product {\n ... on PhysicalProductInterface {\n weight\n }\n sku\n color\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n\n\n }\n attributes {\n label\n code\n value_index\n }\n }\n }\n }\n }\n}","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38897,7 +38897,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${bundle_product_sku}\"} }, sort: {name: ASC}) {\n total_count\n items {\n ... on PhysicalProductInterface {\n weight\n }\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on BundleProduct {\n weight\n price_view\n dynamic_price\n dynamic_sku\n ship_bundle_items\n dynamic_weight\n items {\n option_id\n title\n required\n type\n position\n sku\n options {\n id\n qty\n position\n is_default\n price\n price_type\n can_change_quantity\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -38966,7 +38966,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${downloadable_product_sku}\" } }, sort: {name: ASC})\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n ... on DownloadableProduct {\n links_purchased_separately\n links_title\n downloadable_product_samples {\n id\n title\n sort_order\n sample_type\n sample_file\n sample_url\n }\n downloadable_product_links {\n id\n title\n sort_order\n is_shareable\n price\n number_of_downloads\n link_type\n sample_type\n sample_file\n sample_url\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -39053,7 +39053,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } })\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\n products(filter: {sku: { eq: \"${virtual_product_sku}\" } },sort: {name: ASC})\n {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on PhysicalProductInterface {\n \t\t\tweight\n \t\t }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -39120,7 +39120,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> + <stringProp name="Argument.value">{"query":"{\nproducts(filter: {sku: {eq:\"${grouped_product_sku}\"} }, sort: {name: ASC}) {\n total_count\n items {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n ... on GroupedProduct {\n weight\n items {\n qty\n position\n product {\n attribute_set_id\n categories\n {\n id\n position\n }\n country_of_manufacture\n created_at\n description {\n html\n }\n gift_message_available\n id\n image\n {\n url\n label\n }\n meta_description\n meta_keyword\n meta_title\n media_gallery_entries\n {\n disabled\n file\n id\n label\n media_type\n position\n types\n content\n {\n base64_encoded_data\n type\n name\n }\n video_content\n {\n media_type\n video_description\n video_metadata\n video_provider\n video_title\n video_url\n }\n }\n name\n new_from_date\n new_to_date\n options_container\n ... on CustomizableProductInterface {\n options\n {\n title\n required\n sort_order\n }\n }\n \n price {\n minimalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n maximalPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n regularPrice {\n amount {\n value\n currency\n }\n adjustments {\n amount {\n value\n currency\n }\n code\n description\n }\n }\n }\n product_links\n {\n link_type\n linked_product_sku\n linked_product_type\n position\n sku\n }\n short_description {\n html\n }\n sku\n small_image\n {\n url\n label\n }\n special_from_date\n special_price\n special_to_date\n swatch_image\n thumbnail\n {\n url\n label\n }\n tier_price\n tier_prices\n {\n customer_group_id\n percentage_value\n qty\n value\n website_id\n }\n type_id\n updated_at\n url_key\n url_path\n websites { id name code sort_order default_group_id is_default }\n }\n }\n }\n }\n }\n}\n","variables":null,"operationName":null}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -39536,7 +39536,7 @@ vars.putObject("category", categories[number]); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query category($id: Int!, $currentPage: Int, $pageSize: Int) {\n category(id: $id) {\n product_count\n description\n url_key\n name\n id\n breadcrumbs {\n category_name\n category_url_key\n __typename\n }\n products(pageSize: $pageSize, currentPage: $currentPage) {\n total_count\n items {\n id\n name\n # small_image\n # short_description\n url_key\n special_price\n special_from_date\n special_to_date\n price {\n regularPrice {\n amount {\n value\n currency\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n","variables":{"id":${category_id},"currentPage":1,"pageSize":12},"operationName":"category"}</stringProp> + <stringProp name="Argument.value">{"query":"query category($id: Int!, $currentPage: Int, $pageSize: Int) {\n category(id: $id) {\n product_count\n description\n url_key\n name\n id\n breadcrumbs {\n category_name\n category_url_key\n __typename\n }\n products(pageSize: $pageSize, currentPage: $currentPage, sort: {name: ASC}) {\n total_count\n items {\n id\n name\n # small_image\n # short_description\n url_key\n special_price\n special_from_date\n special_to_date\n price {\n regularPrice {\n amount {\n value\n currency\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n","variables":{"id":${category_id},"currentPage":1,"pageSize":12},"operationName":"category"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -39660,7 +39660,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }, sort: {name: ASC}) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -39784,7 +39784,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($url_key: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $url_key } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($product_sku: String, $onServer: Boolean!) {\n productDetail: products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -39908,7 +39908,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }, sort: {name: ASC}) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40032,7 +40032,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($product_sku: String, $onServer: Boolean!) {\n products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40154,7 +40154,7 @@ vars.putObject("category", categories[number]); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(\n pageSize:12\n search: $inputText, filter: { category_id: { eq: $categoryId } }) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> + <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(\n pageSize:12\n search: $inputText, filter: { category_id: { eq: $categoryId } }, sort: {name: ASC}) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -40298,7 +40298,7 @@ vars.putObject("category", categories[number]); <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> <stringProp name="Argument.value"> - {"query":"query categoryList($id: Int!) {\n category(id: $id) {\n id\n children {\n id\n name\n url_key\n url_path\n children_count\n path\n image\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"categoryList"} + {"query":"query categoryList($id: Int!) {\n category(id: $id) {\n id\n children {\n id\n name\n url_key\n url_path\n children_count\n path\n image\n productImagePreview: products(pageSize: 1, sort: {name: ASC}) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"categoryList"} </stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> @@ -40688,7 +40688,7 @@ vars.putObject("category", categories[number]); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> + <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1, sort: {name: ASC}) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -41674,7 +41674,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($product_sku: String, $onServer: Boolean!) {\n products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -42152,7 +42152,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($product_sku: String, $onServer: Boolean!) {\n products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -42716,7 +42716,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($product_sku: String, $onServer: Boolean!) {\n products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43466,7 +43466,7 @@ vars.putObject("category", categories[number]); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> + <stringProp name="Argument.value">{"query":"query navigationMenu($id: Int!) {\n category(id: $id) {\n id\n name\n product_count\n path\n children {\n id\n name\n position\n level\n url_key\n url_path\n product_count\n children_count\n path\n productImagePreview: products(pageSize: 1, sort: {name: ASC}) {\n items {\n small_image {\n label\n url\n }\n }\n }\n }\n }\n}","variables":{"id":${category_id}},"operationName":"navigationMenu"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43505,7 +43505,7 @@ vars.putObject("category", categories[number]); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(\n pageSize:12\n search: $inputText, filter: { category_id: { eq: $categoryId } }) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> + <stringProp name="Argument.value">{"query":"query productSearch($inputText: String!, $categoryId: String) {\n products(\n pageSize:12\n search: $inputText, filter: { category_id: { eq: $categoryId } }, sort: {name: ASC}) {\n items {\n id\n name\n small_image {\n label\n url\n }\n url_key\n price {\n regularPrice {\n amount {\n value\n currency\n }\n }\n }\n }\n total_count\n filters {\n name\n filter_items_count\n request_var\n filter_items {\n label\n value_string\n }\n }\n }\n}","variables":{"inputText":"Product","categoryId":"${category_id}"},"operationName":"productSearch"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43605,7 +43605,7 @@ if (totalCount == null) { <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query category($id: Int!, $currentPage: Int, $pageSize: Int) {\n category(id: $id) {\n product_count\n description\n url_key\n name\n id\n breadcrumbs {\n category_name\n category_url_key\n __typename\n }\n products(pageSize: $pageSize, currentPage: $currentPage) {\n total_count\n items {\n id\n name\n # small_image\n # short_description\n url_key\n special_price\n special_from_date\n special_to_date\n price {\n regularPrice {\n amount {\n value\n currency\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n","variables":{"id":${category_id},"currentPage":1,"pageSize":12},"operationName":"category"}</stringProp> + <stringProp name="Argument.value">{"query":"query category($id: Int!, $currentPage: Int, $pageSize: Int) {\n category(id: $id) {\n product_count\n description\n url_key\n name\n id\n breadcrumbs {\n category_name\n category_url_key\n __typename\n }\n products(pageSize: $pageSize, currentPage: $currentPage, sort: {name: ASC}) {\n total_count\n items {\n id\n name\n # small_image\n # short_description\n url_key\n special_price\n special_from_date\n special_to_date\n price {\n regularPrice {\n amount {\n value\n currency\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n __typename\n }\n}\n","variables":{"id":${category_id},"currentPage":1,"pageSize":12},"operationName":"category"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43664,7 +43664,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($product_sku: String, $onServer: Boolean!) {\n products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43703,7 +43703,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }, sort: {name: ASC}) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43762,7 +43762,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($url_key: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $url_key } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($product_sku: String, $onServer: Boolean!) {\n productDetail: products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -43801,7 +43801,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetail($urlKey: String, $onServer: Boolean!) {\n productDetail: products(filter: { url_key: { eq: $urlKey } }, sort: {name: ASC}) {\n items {\n sku\n name\n price {\n regularPrice {\n amount {\n currency\n value\n }\n }\n }\n description {html}\n media_gallery_entries {\n label\n position\n disabled\n file\n }\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n # Yes, Products have `meta_keyword` and\n # everything else has `meta_keywords`.\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"urlKey":"${product_url_key}","onServer":false},"operationName":"productDetail"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> @@ -44066,7 +44066,7 @@ vars.put("product_sku", product.get("sku")); <collectionProp name="Arguments.arguments"> <elementProp name="" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> - <stringProp name="Argument.value">{"query":"query productDetailByName($url_key: String, $onServer: Boolean!) {\n products(filter: { url_key: { eq: $url_key } }) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"url_key":"${product_url_key}","onServer":false},"operationName":"productDetailByName"}</stringProp> + <stringProp name="Argument.value">{"query":"query productDetailByName($product_sku: String, $onServer: Boolean!) {\n products(filter: { sku: { eq: $product_sku } }, sort: {name: ASC}) {\n items {\n id\n sku\n name\n ... on ConfigurableProduct {\n configurable_options {\n attribute_code\n attribute_id\n id\n label\n values {\n default_label\n label\n store_label\n use_default_value\n value_index\n }\n }\n variants {\n product {\n #fashion_color\n #fashion_size\n id\n media_gallery_entries {\n disabled\n file\n label\n position\n }\n sku\n stock_status\n }\n }\n }\n meta_title @include(if: $onServer)\n meta_keyword @include(if: $onServer)\n meta_description @include(if: $onServer)\n }\n }\n}","variables":{"product_sku":"${product_sku}","onServer":false},"operationName":"productDetailByName"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> </elementProp> </collectionProp> From eb530715941be2a368714bc5afaee8e472224362 Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Thu, 12 Sep 2019 15:31:47 -0500 Subject: [PATCH 775/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - review comments --- .../Magento/GraphQl/Catalog/ProductSearchTest.php | 8 ++++++-- .../Catalog/_files/products_for_relevance_sorting.php | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 8b70a31f04588..859e83e2a94e3 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -302,7 +302,11 @@ public function testFilterProductsByDropDownCustomAttribute() ] ); } - private function reIndexAndCleanCache() + + /** + * @return void + */ + private function reIndexAndCleanCache() : void { $objectManager = Bootstrap::getObjectManager(); $indexer = $objectManager->create(Indexer::class); @@ -1558,7 +1562,7 @@ public function testFilterByExactSkuAndSortByPriceDesc() */ public function testProductBasicFullTextSearchQuery() { - $this->reIndexAndCleanCache(); + $this->reIndexAndCleanCache(); $textToSearch = 'blue'; $query =<<<QUERY diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php index 9e84178dc4c89..87a80dfba168c 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php @@ -65,10 +65,10 @@ ->setSku('navy-striped-shoes') ->setPrice(40) ->setWeight(8) - ->setDescription('Blue striped flip flops at <b>one</b>') + ->setDescription('blue striped flip flops at <b>one</b>') ->setMetaTitle('navy blue colored shoes meta title') ->setMetaKeyword('blue, navy, striped , women, kids') - ->setMetaDescription('blue', 'shoes women kids meta description') + ->setMetaDescription('blue shoes women kids meta description') ->setStockData(['use_config_manage_stock' => 0]) ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) @@ -96,7 +96,7 @@ /** @var \Magento\Catalog\Model\Product $greyProduct */ $greyProduct = $productRepository->get('grey_shorts'); -$greyProduct->setDescription('Description with blue lines'); +$greyProduct->setDescription('Description with Blue lines'); $productRepository->save($greyProduct); $skus = ['green_socks', 'white_shorts','red_trousers','blue_briefs','grey_shorts', From b8f70d2e7e58484f58d65beed8a73d9b9ccbfcee Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko <ymiroshnychenko@magento.com> Date: Thu, 12 Sep 2019 16:12:30 -0500 Subject: [PATCH 776/841] MC-19421: Reduce q-ty of Reports Created in /app/*/var/report --- pub/errors/processor.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pub/errors/processor.php b/pub/errors/processor.php index a0410ce60c346..889eed2cad10e 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -507,10 +507,6 @@ public function saveReport(array $reportData): string } $this->_setReportData($reportData); - if (!file_exists($this->_reportDir)) { - @mkdir($this->_reportDir, 0777, true); - } - @file_put_contents($this->_reportFile, $this->serializer->serialize($reportData). PHP_EOL); if (isset($reportData['skin']) && self::DEFAULT_SKIN != $reportData['skin']) { From 9902f5111d9adaa14d7edbcda6cb1bfa6fb2f48e Mon Sep 17 00:00:00 2001 From: Deepty Thampy <dthampy@adobe.com> Date: Thu, 12 Sep 2019 17:41:08 -0500 Subject: [PATCH 777/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - review comments --- .../Catalog/_files/products_for_relevance_sorting_rollback.php | 3 +-- ...ducts_with_layered_navigation_custom_attribute_rollback.php | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php index 888687ec80d6c..5a1dd30c6b492 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting_rollback.php @@ -4,7 +4,6 @@ * See COPYING.txt for license details. */ - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var \Magento\Framework\Registry $registry */ $registry = $objectManager->get(\Magento\Framework\Registry::class); @@ -12,7 +11,7 @@ $registry->register('isSecureArea', true); /** @var $category \Magento\Catalog\Model\Category */ -$category = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Category::class); +$category = $objectManager->create(\Magento\Catalog\Model\Category::class); $category->load(330); if ($category->getId()) { $category->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php index 4212075c312c9..5cababbc988c7 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_with_layered_navigation_custom_attribute_rollback.php @@ -25,7 +25,7 @@ $attributeRepository->delete($attribute); } /** @var $product \Magento\Catalog\Model\Product */ -$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$objectManager = Bootstrap::getObjectManager(); $entityType = $objectManager->create(\Magento\Eav\Model\Entity\Type::class)->loadByCode('catalog_product'); From 7a199d40ee8086b604153ef2b4d44b9591d12866 Mon Sep 17 00:00:00 2001 From: Stas Kozar <stas.kozar@transoftgroup.com> Date: Fri, 13 Sep 2019 11:41:54 +0300 Subject: [PATCH 778/841] MC-11329: Storefront Navigation Menu UI, desktop --- .../ActionGroup/AdminCategoryActionGroup.xml | 25 ++ .../Catalog/Test/Mftf/Data/CategoryData.xml | 3 + ...rontCatalogNavigationMenuUIDesktopTest.xml | 335 ++++++++++++++++++ .../AdminChangeMagentoThemeActionGroup.xml | 23 ++ ...StorefrontCheckElementColorActionGroup.xml | 24 ++ .../Theme/Test/Mftf/Data/DesignData.xml | 6 + .../Mftf/Data/NavigationMenuColorData.xml | 16 + .../Mftf/Section/AdminDesignConfigSection.xml | 1 + .../StorefrontNavigationMenuSection.xml | 21 ++ 9 files changed, 454 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml create mode 100644 app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml create mode 100644 app/code/Magento/Theme/Test/Mftf/ActionGroup/StorefrontCheckElementColorActionGroup.xml create mode 100644 app/code/Magento/Theme/Test/Mftf/Data/NavigationMenuColorData.xml create mode 100644 app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml index 12bf5179a07d0..419e3778f2128 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml @@ -396,4 +396,29 @@ <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> <waitForPageLoad stepKey="waitForPageToLoad1"/> </actionGroup> + + <actionGroup name="DeleteDefaultCategoryChildren"> + <annotations> + <description>Deletes all children categories of Default Root Category.</description> + </annotations> + + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToAdminCategoryPage"/> + <executeInSelenium function="function ($webdriver) use ($I) { + $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., + \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); + while (!empty($children)) { + $I->click('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., + \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a'); + $I->waitForPageLoad(30); + $I->click('#delete'); + $I->waitForElementVisible('aside.confirm .modal-footer button.action-accept'); + $I->click('aside.confirm .modal-footer button.action-accept'); + $I->waitForPageLoad(30); + $I->waitForElementVisible('#messages div.message-success', 30); + $I->see('You deleted the category.', '#messages div.message-success'); + $children = $webdriver->findElements(\Facebook\WebDriver\WebDriverBy::xpath('//ul[contains(@class, \'x-tree-node-ct\')]/li[@class=\'x-tree-node\' and contains(., + \'{{DefaultCategory.name}}\')]/ul[contains(@class, \'x-tree-node-ct\')]/li//a')); + } + }" stepKey="deleteAllChildCategories"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 13951a0d197d1..336cf2d41ef02 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -117,4 +117,7 @@ <data key="is_active">true</data> <data key="include_in_menu">true</data> </entity> + <entity name="DefaultCategory" type="category"> + <data key="name">Default Category</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml new file mode 100644 index 0000000000000..4df0e151aeab4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml @@ -0,0 +1,335 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCatalogNavigationMenuUIDesktopTest"> + <annotations> + <features value="Catalog"/> + <stories value="Storefront Catalog Navigation Menu UI"/> + <title value="Storefront Catalog Navigation Menu UI, desktop"/> + <description value="Verify UI of Navigation Menu functionality on Storefront"/> + <testCaseId value="MC-11329"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="DeleteDefaultCategoryChildren" stepKey="deleteRootCategoryChildren"/> + </before> + <after> + <actionGroup ref="DeleteDefaultCategoryChildren" stepKey="deleteRootCategoryChildren"/> + <actionGroup ref="AdminChangeMagentoThemeActionGroup" stepKey="changeThemeToDefault"> + <argument name="theme" value="{{MagentoLumaTheme.name}}"/> + </actionGroup> + <!-- Admin log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Go to Content > Themes. Change theme to Blank --> + <actionGroup ref="AdminChangeMagentoThemeActionGroup" stepKey="changeThemeToBlank"> + <argument name="theme" value="{{MagentoBlankTheme.name}}"/> + </actionGroup> + + <!-- Open storefront --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStorefrontPage"/> + + <!-- Assert no category - no menu --> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.navigationMenu}}" stepKey="dontSeeMenu"/> + + <!-- Assert single row - no hover state --> + <createData entity="ApiCategory" stepKey="createFirstCategoryBlank"> + <field key="name">Category A</field> + </createData> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForBlankSingleRowAppear"/> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createFirstCategoryBlank.name$$)}}" stepKey="hoverFirstCategoryBlank"/> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.subItemLevelHover('level0')}}" stepKey="assertNoHoverState"/> + + <!-- Create categories --> + <createData entity="ApiCategory" stepKey="createSecondCategoryBlank"> + <field key="name">TEST</field> + </createData> + <createData entity="ApiCategory" stepKey="createThirdCategoryBlank"> + <field key="name">_test2</field> + </createData> + <createData entity="ApiCategory" stepKey="createFourthCategoryBlank"> + <field key="name">test 3</field> + </createData> + <createData entity="ApiCategory" stepKey="createFifthCategoryBlank"> + <field key="name">Category with several products</field> + </createData> + <createData entity="ApiCategory" stepKey="createSixthCategoryBlank"> + <field key="name">test 5</field> + </createData> + <createData entity="ApiCategory" stepKey="createSeventhCategoryBlank"> + <field key="name">test 8</field> + </createData> + <createData entity="ApiCategory" stepKey="createEighthCategoryBlank"> + <field key="name">This is a very very very very very looong title</field> + </createData> + <createData entity="ApiCategory" stepKey="createNinthCategoryBlank"> + <field key="name">test 6</field> + </createData> + <createData entity="ApiCategory" stepKey="createTenthCategoryBlank"> + <field key="name">test 7</field> + </createData> + <createData entity="ApiCategory" stepKey="createEleventhCategoryBlank"> + <field key="name">test 4</field> + </createData> + <createData entity="ApiCategory" stepKey="createTwelfthCategoryBlank"> + <field key="name">Category with image</field> + </createData> + <createData entity="ApiCategory" stepKey="createThirteenthCategoryBlank"> + <field key="name">test 0</field> + </createData> + <createData entity="ApiCategory" stepKey="createCategoryWithoutChildrenBlank"> + <field key="name">Category with description & custom title</field> + </createData> + <createData entity="ApiCategory" stepKey="createCategoryWithChildrenBlank"> + <field key="name">Category with children</field> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createFirstCategoryLevelOneBlank"> + <field key="name">level 1 test category very very very long name</field> + <requiredEntity createDataKey="createCategoryWithChildrenBlank"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createSecondCategoryLevelOneBlank"> + <field key="name">level 1 test category name</field> + <requiredEntity createDataKey="createCategoryWithChildrenBlank"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createThirdCategoryLevelOneBlank"> + <field key="name">level 1 with children</field> + <requiredEntity createDataKey="createCategoryWithChildrenBlank"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelTwoBlank"> + <field key="name">level 2 with children</field> + <requiredEntity createDataKey="createThirdCategoryLevelOneBlank"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelThreeBlank"> + <field key="name">level 3 test</field> + <requiredEntity createDataKey="createCategoryLevelTwoBlank"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createFirstCategoryLevelFourBlank"> + <field key="name">level 4</field> + <requiredEntity createDataKey="createCategoryLevelThreeBlank"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createSecondCategoryLevelFourBlank"> + <field key="name">level 4 test</field> + <requiredEntity createDataKey="createCategoryLevelThreeBlank"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelFiveBlank"> + <field key="name">level 5</field> + <requiredEntity createDataKey="createSecondCategoryLevelFourBlank"/> + </createData> + + <!-- Several rows. Hover on category without children --> + <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForBlankSeveralRowsAppear"/> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryWithoutChildrenBlank.name$$)}}" stepKey="hoverCategoryWithoutChildren"/> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.itemByNameAndLevel($$createCategoryWithoutChildrenBlank.name$$, 'level0')}}" stepKey="dontSeeChildrenInCategory"/> + + <!-- Nested level 1. No hover state --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryWithChildrenBlank.name$$)}}" stepKey="hoverCategoryWithChildrenTopLevel"/> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkNoHoverState"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemByLevel('level0')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.white}}"/> + </actionGroup> + + <!-- Nested level 1. Hover state on 1st item --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createFirstCategoryLevelOneBlank.name$$)}}" stepKey="hoverCategoryLevelOneFirstItem"/> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkHighlightedAfterHoverFirstItem"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level0')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.gray}}"/> + </actionGroup> + + <!-- Nested level 1 & 2. Hover state on the last item --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createThirdCategoryLevelOneBlank.name$$)}}" stepKey="hoverCategoryLevelOneLastItem"/> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkHighlightedAfterHoverLastItem"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level0')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.gray}}"/> + </actionGroup> + + <!-- Submenu appears rightward --> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level0')}}" stepKey="assertTopLevelMenuLeftDirection"/> + + <!-- Nested level 1 & 5 --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelTwoBlank.name$$)}}" stepKey="hoverCategoryLevelTwo"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuLeftDirection('level1')}}" stepKey="seeLevelOneMenuLeftDirection"/> + + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelThreeBlank.name$$)}}" stepKey="hoverCategoryLevelThree"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuLeftDirection('level2')}}" stepKey="seeLevelTwoMenuRightDirection"/> + + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createSecondCategoryLevelFourBlank.name$$)}}" stepKey="hoverCategoryLevelFour"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level3')}}" stepKey="seeLevelThreeMenuRightDirection"/> + + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkSubcategoryHighlighted"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level3')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.gray}}"/> + </actionGroup> + + <!-- Delete all creation for Blank theme --> + <deleteData createDataKey="createFirstCategoryBlank" stepKey="deleteFirstCategoryBlank"/> + <deleteData createDataKey="createSecondCategoryBlank" stepKey="deleteSecondCategoryBlank"/> + <deleteData createDataKey="createThirdCategoryBlank" stepKey="deleteThirdCategoryBlank"/> + <deleteData createDataKey="createFourthCategoryBlank" stepKey="deleteFourthCategoryBlank"/> + <deleteData createDataKey="createFifthCategoryBlank" stepKey="deleteFifthCategoryBlank"/> + <deleteData createDataKey="createSixthCategoryBlank" stepKey="deleteSixthCategoryBlank"/> + <deleteData createDataKey="createSeventhCategoryBlank" stepKey="deleteSeventhCategoryBlank"/> + <deleteData createDataKey="createEighthCategoryBlank" stepKey="deleteEighthCategoryBlank"/> + <deleteData createDataKey="createNinthCategoryBlank" stepKey="deleteNinthCategoryBlank"/> + <deleteData createDataKey="createTenthCategoryBlank" stepKey="deleteTenthCategoryBlank"/> + <deleteData createDataKey="createEleventhCategoryBlank" stepKey="deleteEleventhCategoryBlank"/> + <deleteData createDataKey="createTwelfthCategoryBlank" stepKey="deleteTwelfthCategoryBlank"/> + <deleteData createDataKey="createThirteenthCategoryBlank" stepKey="deleteThirteenthCategoryBlank"/> + <deleteData createDataKey="createCategoryWithChildrenBlank" stepKey="deleteCategoryWithChildrenBlank"/> + <deleteData createDataKey="createCategoryWithoutChildrenBlank" stepKey="deleteCategoryWithoutChildrenBlank"/> + + <!-- Go to Content > Themes. Change theme to Luma --> + <actionGroup ref="AdminChangeMagentoThemeActionGroup" stepKey="changeThemeToLuma"> + <argument name="theme" value="{{MagentoLumaTheme.name}}"/> + </actionGroup> + + <!-- Open storefront --> + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStorefront"/> + + <!-- Assert no category - no menu --> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.navigationMenu}}" stepKey="dontSeeMenuOnStorefront"/> + + <!-- Create categories --> + <createData entity="ApiCategory" stepKey="createFirstCategoryLuma"/> + <createData entity="ApiCategory" stepKey="createSecondCategoryLuma"/> + <createData entity="ApiCategory" stepKey="createThirdCategoryLuma"/> + <createData entity="ApiCategory" stepKey="createFourthCategoryLuma"/> + + <!-- Single row. No hover state --> + <reloadPage stepKey="reload"/> + <waitForPageLoad stepKey="waitForLumaSingleRowAppear"/> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.itemByNameAndLevel($$createFirstCategoryLuma.name$$, 'level0')}}" stepKey="noHoverStateInFirstCategory"/> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.itemByNameAndLevel($$createSecondCategoryLuma.name$$, 'level0')}}" stepKey="noHoverStateInSecondCategory"/> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.itemByNameAndLevel($$createThirdCategoryLuma.name$$, 'level0')}}" stepKey="noHoverStateThirdCategory"/> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.itemByNameAndLevel($$createFourthCategoryLuma.name$$, 'level0')}}" stepKey="noHoverStateInFourthCategory"/> + + <!-- Create categories for testing Luma theme --> + <createData entity="ApiCategory" stepKey="createFifthCategoryLuma"/> + <createData entity="ApiCategory" stepKey="createCategoryWithChildrenLuma"/> + <createData entity="SubCategoryWithParent" stepKey="createFirstCategoryLevelOneLuma"> + <requiredEntity createDataKey="createCategoryWithChildrenLuma"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createSecondCategoryLevelOneLuma"> + <requiredEntity createDataKey="createCategoryWithChildrenLuma"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createThirdCategoryLevelOneLuma"> + <requiredEntity createDataKey="createCategoryWithChildrenLuma"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createFirstCategoryLevelTwoLuma"> + <requiredEntity createDataKey="createThirdCategoryLevelOneLuma"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createSecondCategoryLevelTwoLuma"> + <requiredEntity createDataKey="createThirdCategoryLevelOneLuma"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelThreeLuma"> + <requiredEntity createDataKey="createSecondCategoryLevelTwoLuma"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="createCategoryLevelFourLuma"> + <requiredEntity createDataKey="createCategoryLevelThreeLuma"/> + </createData> + <createData entity="ApiCategory" stepKey="createSixthCategoryLuma"/> + <createData entity="ApiCategory" stepKey="createSeventhCategoryLuma"/> + <createData entity="ApiCategory" stepKey="createEighthCategoryLuma"/> + + <!-- Several rows. Hover on Category without children --> + <reloadPage stepKey="refresh"/> + <waitForPageLoad stepKey="waitForLumaSeveralRowsAppear"/> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createFifthCategoryLuma.name$$)}}" stepKey="hoverOnCategoryWithoutChildren"/> + <dontSeeElement selector="{{StorefrontNavigationMenuSection.itemByNameAndLevel($$createFifthCategoryLuma.name$$, 'level0')}}" stepKey="dontSeeSubcategoriesInCategory"/> + + <!-- Nested level 1. No hover state --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryWithChildrenLuma.name$$)}}" stepKey="hoverOnCategoryWithChildren"/> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkNoHighlightedInSubmenuAfterHover"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemByLevel('level0')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.white}}"/> + </actionGroup> + + <!-- Nested level 1. Hover state on first item --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createFirstCategoryLevelOneLuma.name$$)}}" stepKey="hoverOnFirstItemLevelOne"/> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkHighlightedAfterHoverOnFirstItem"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level0')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.gray}}"/> + </actionGroup> + + <!-- Nested levels 1 & 2. Hover state on last item --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createThirdCategoryLevelOneLuma.name$$)}}" stepKey="hoverOnLastItemLevelOne"/> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkHighlightedAfterHoverOnLastItem"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level0')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.gray}}"/> + </actionGroup> + + <!-- Submenu appears rightward --> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level0')}}" stepKey="seeTopLevelRightDirection"/> + + <!-- Nested levels 1 & 5 --> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createSecondCategoryLevelTwoLuma.name$$)}}" stepKey="hoverThirdCategoryLevelTwo"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level1')}}" stepKey="seeFirstLevelRightDirection"/> + + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelThreeLuma.name$$)}}" stepKey="hoverOnCategoryLevelThree"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level2')}}" stepKey="seeSecondLevelRightDirection"/> + + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryLevelFourLuma.name$$)}}" stepKey="hoverOnCategoryLevelFour"/> + <seeElement selector="{{StorefrontNavigationMenuSection.submenuRightDirection('level3')}}" stepKey="seeThirdLevelRightDirection"/> + + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkSubcategoryHighlightedAfterHover"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemLevelHover('level3')}}"/> + <argument name="property" value="background-color"/> + <argument name="color" value="{{NavigationMenuColor.gray}}"/> + </actionGroup> + + <!-- Selected 1st level category --> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryWithChildrenLuma.name$$)}}" stepKey="openTopLevelCategory"/> + <waitForPageLoad stepKey="waitForCategoryPageLoaded"/> + + <!-- Assert category active state --> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkCategoryActiveState"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.itemActiveState}}"/> + <argument name="property" value="border-color"/> + <argument name="color" value="{{NavigationMenuColor.orange}}"/> + </actionGroup> + + <!-- Selected subcategory. Assert active state --> + <actionGroup ref="StorefrontGoToSubCategoryPageActionGroup" stepKey="openSubcategory"> + <argument name="categoryName" value="$$createCategoryWithChildrenLuma.name$$"/> + <argument name="subCategoryName" value="$$createThirdCategoryLevelOneLuma.name$$"/> + </actionGroup> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategoryWithChildrenLuma.name$$)}}" stepKey="hoverOnCategory"/> + <moveMouseOver selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createThirdCategoryLevelOneLuma.name$$)}}" stepKey="hoverOnSubcategory"/> + + <!-- Assert subcategory active state --> + <actionGroup ref="StorefrontCheckElementColorActionGroup" stepKey="checkSubitemActiveState"> + <argument name="selector" value="{{StorefrontNavigationMenuSection.subItemActiveState}}"/> + <argument name="property" value="border-color"/> + <argument name="color" value="{{NavigationMenuColor.orange}}"/> + </actionGroup> + + <!-- Delete created category --> + <deleteData createDataKey="createFirstCategoryLuma" stepKey="deleteFirstCategoryLuma"/> + <deleteData createDataKey="createSecondCategoryLuma" stepKey="deleteSecondCategoryLuma"/> + <deleteData createDataKey="createThirdCategoryLuma" stepKey="deleteThirdCategoryLuma"/> + <deleteData createDataKey="createFourthCategoryLuma" stepKey="deleteFourthCategoryLuma"/> + <deleteData createDataKey="createFifthCategoryLuma" stepKey="deleteFifthCategoryLuma"/> + <deleteData createDataKey="createSixthCategoryLuma" stepKey="deleteSixthCategoryLuma"/> + <deleteData createDataKey="createSeventhCategoryLuma" stepKey="deleteSeventhCategoryLuma"/> + <deleteData createDataKey="createEighthCategoryLuma" stepKey="deleteEighthCategoryLuma"/> + <deleteData createDataKey="createCategoryWithChildrenLuma" stepKey="deleteCategoryWithChildrenLuma"/> + </test> +</tests> diff --git a/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml b/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml new file mode 100644 index 0000000000000..0b093a1e0e8d8 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminChangeMagentoThemeActionGroup"> + <arguments> + <argument name="theme" type="string"/> + </arguments> + <amOnPage url="{{DesignConfigPage.url}}" stepKey="navigateToDesignConfigPage"/> + <click selector="{{AdminDesignConfigSection.scopeRow('3')}}" stepKey="editStoreView"/> + <waitForPageLoad stepKey="waitForOpen"/> + <selectOption selector="{{AdminDesignConfigSection.appliedTheme}}" userInput="{{theme}}" stepKey="selectTheme"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSave"/> + <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessage"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Theme/Test/Mftf/ActionGroup/StorefrontCheckElementColorActionGroup.xml b/app/code/Magento/Theme/Test/Mftf/ActionGroup/StorefrontCheckElementColorActionGroup.xml new file mode 100644 index 0000000000000..66e98d5e41527 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/ActionGroup/StorefrontCheckElementColorActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCheckElementColorActionGroup"> + <annotations> + <description>Checks element color on storefront.</description> + </annotations> + <arguments> + <argument name="selector" type="string"/> + <argument name="property" type="string"/> + <argument name="color" type="string"/> + </arguments> + + <executeJS function="return window.getComputedStyle(document.querySelector('{{selector}}')).getPropertyValue('{{property}}')" stepKey="getElementColor"/> + <assertEquals expected="{{color}}" expectedType="string" actualType="variable" actual="getElementColor" message="pass" stepKey="assertElementColor"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Theme/Test/Mftf/Data/DesignData.xml b/app/code/Magento/Theme/Test/Mftf/Data/DesignData.xml index ec28e8ed7a999..1a3c10745f5a7 100644 --- a/app/code/Magento/Theme/Test/Mftf/Data/DesignData.xml +++ b/app/code/Magento/Theme/Test/Mftf/Data/DesignData.xml @@ -11,4 +11,10 @@ <entity name="Layout" type="page_layout"> <data key="1column">1 column</data> </entity> + <entity name="MagentoBlankTheme" type="theme"> + <data key="name">Magento Blank</data> + </entity> + <entity name="MagentoLumaTheme" type="theme"> + <data key="name">Magento Luma</data> + </entity> </entities> diff --git a/app/code/Magento/Theme/Test/Mftf/Data/NavigationMenuColorData.xml b/app/code/Magento/Theme/Test/Mftf/Data/NavigationMenuColorData.xml new file mode 100644 index 0000000000000..7af07753d7c9c --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/Data/NavigationMenuColorData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="NavigationMenuColor" type="navigation_menu_color"> + <data key="gray">rgb(232, 232, 232)</data> + <data key="white">rgb(255, 255, 255)</data> + <data key="orange">rgb(255, 85, 1)</data> + </entity> +</entities> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml index c2652f33f7606..8004d6d6c344f 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml @@ -31,5 +31,6 @@ <element name="storesArrow" type="button" selector="#ZmF2aWNvbi9zdG9yZXM- > .jstree-icon" /> <element name="checkIfStoresArrowExpand" type="button" selector="//li[@id='ZmF2aWNvbi9zdG9yZXM-' and contains(@class,'jstree-closed')]" /> <element name="storeLink" type="button" selector="#ZmF2aWNvbi9zdG9yZXMvMQ-- > a"/> + <element name="appliedTheme" type="select" selector="select[name='theme_theme_id']"/> </section> </sections> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml new file mode 100644 index 0000000000000..fd4b0ce453634 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontNavigationMenuSection"> + <element name="navigationMenu" type="block" selector=".section-items.nav-sections-items li"/> + <element name="subItemLevelHover" type="text" selector=".{{level}} .submenu a:hover" parameterized="true"/> + <element name="itemByNameAndLevel" type="text" selector="//a[span[contains(., '{{categoryName}}')]]/following-sibling::ul[contains(@class,'{{categoryLevel}}')]" parameterized="true"/> + <element name="subItemByLevel" type="text" selector="li.{{itemLevel}}.parent ul.{{itemLevel}}" parameterized="true"/> + <element name="itemActiveState" type="text" selector=".navigation .level0.active>.level-top"/> + <element name="subItemActiveState" type="text" selector=".navigation .level0 .submenu .active>a"/> + <element name="submenuLeftDirection" type="text" selector="ul.{{itemLevel}}.submenu-reverse" parameterized="true"/> + <element name="submenuRightDirection" type="text" selector="ul.{{itemLevel}}:not(.submenu-reverse)" parameterized="true"/> + </section> +</sections> From 807cb5cc9babed76a93f99b5b917d8db03939adb Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Fri, 13 Sep 2019 11:42:28 +0300 Subject: [PATCH 779/841] MC-17606: Unable to save edited product when max_sale_qty is Magento's default --- ...ddConfigurableProductToShoppingCartTest.xml | 1 + ...StoreFrontCheckingWithMultishipmentTest.xml | 1 + ...toreFrontCheckingWithSingleShipmentTest.xml | 1 + ...StoreFrontMinicartWithMultishipmentTest.xml | 18 ++++++++++-------- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml index 18e4cd7db2871..e3090d6cb311b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontAddConfigurableProductToShoppingCartTest.xml @@ -21,6 +21,7 @@ <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> <magentoCLI command="config:set {{EnableFlatRateDefaultPriceConfigData.path}} {{EnableFlatRateDefaultPriceConfigData.value}}" stepKey="enableFlatRatePrice"/> <magentoCLI command="config:set {{EnableFlatRateToAllAllowedCountriesConfigData.path}} {{EnableFlatRateToAllAllowedCountriesConfigData.value}}" stepKey="allowFlatRateToAllCountries"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShipping"/> <!-- Create Default Category --> <createData entity="_defaultCategory" stepKey="createCategory"/> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml index 271f6e707cd69..3a58ead3b6dfa 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithMultishipmentTest.xml @@ -57,6 +57,7 @@ <deleteData stepKey="deleteProduct1" createDataKey="product1"/> <deleteData stepKey="deleteProduct2" createDataKey="product2"/> <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShipping"/> <actionGroup ref="logout" stepKey="logout"/> </after> </test> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml index f425a44130ecb..c9f1856249762 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontCheckingWithSingleShipmentTest.xml @@ -57,6 +57,7 @@ <deleteData stepKey="deleteProduct1" createDataKey="product1"/> <deleteData stepKey="deleteProduct2" createDataKey="product2"/> <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShipping"/> <actionGroup ref="logout" stepKey="logout"/> </after> </test> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml index 8d5a58acc7e18..d52ddb11212aa 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StoreFrontMinicartWithMultishipmentTest.xml @@ -37,6 +37,16 @@ </actionGroup> </before> + <after> + <deleteData stepKey="deleteCategory" createDataKey="category"/> + <deleteData stepKey="deleteProduct1" createDataKey="product1"/> + <deleteData stepKey="deleteProduct2" createDataKey="product2"/> + <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShipping"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + <actionGroup ref="logout" stepKey="logoutAdmin"/> + </after> + <amOnPage url="$$product1.name$$.html" stepKey="goToProduct1"/> <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProduct1"> <argument name="productName" value="$$product1.name$$"/> @@ -53,13 +63,5 @@ <amOnPage url="/checkout/cart/index/" stepKey="amOnCheckoutCartIndexPage"/> <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCartAgain"/> <actionGroup ref="CheckingWithMinicartActionGroup" stepKey="checkoutWithMinicart"/> - - <after> - <deleteData stepKey="deleteCategory" createDataKey="category"/> - <deleteData stepKey="deleteProduct1" createDataKey="product1"/> - <deleteData stepKey="deleteProduct2" createDataKey="product2"/> - <deleteData stepKey="deleteCustomer" createDataKey="customer"/> - <actionGroup ref="logout" stepKey="logout"/> - </after> </test> </tests> From 4bc0879a78f1bcdaf08b08efeaab09cf2ae678e6 Mon Sep 17 00:00:00 2001 From: Aliaksei Yakimovich2 <aliaksei_yakimovich2@epam.com> Date: Fri, 13 Sep 2019 13:19:18 +0300 Subject: [PATCH 780/841] MAGETWO-70803: [GITHUB] Inconsistent CSV file Import error: #7495 - Fixed static tests; --- .../Magento/CatalogImportExport/Model/Import/Product.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index a8395aac0f4eb..45510d61b616d 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -140,7 +140,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity const COL_PRODUCT_WEBSITES = '_product_websites'; /** - * Media gallery attribute code. + * Attribute code for media gallery. */ const MEDIA_GALLERY_ATTRIBUTE_CODE = 'media_gallery'; @@ -150,12 +150,12 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity const COL_MEDIA_IMAGE = '_media_image'; /** - * Inventory use config. + * Inventory use config label. */ const INVENTORY_USE_CONFIG = 'Use Config'; /** - * Inventory use config prefix. + * Prefix for inventory use config. */ const INVENTORY_USE_CONFIG_PREFIX = 'use_config_'; From d1c4f9c0da86ee059d2e14c94cc776cb2346d096 Mon Sep 17 00:00:00 2001 From: DianaRusin <rusind95@gmail.com> Date: Fri, 13 Sep 2019 13:34:22 +0300 Subject: [PATCH 781/841] MC-11386: Verify Category Product and Product Category partial reindex --- .../Catalog/Test/Mftf/Data/CategoryData.xml | 3 + .../Test/Mftf/Data/CustomAttributeData.xml | 4 + ...ctAndProductCategoryPartialReindexTest.xml | 226 ++++++++++++++++++ 3 files changed, 233 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 13951a0d197d1..05a6bae9ab0fc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -117,4 +117,7 @@ <data key="is_active">true</data> <data key="include_in_menu">true</data> </entity> + <entity name="SubCategoryNonAnchor" extends="SubCategoryWithParent"> + <requiredEntity type="custom_attribute">CustomAttributeCategoryIsAnchor</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml index 1684bd0c8a2c3..69d8c3c87ae5b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml @@ -51,4 +51,8 @@ <data key="attribute_code">short_description</data> <data key="value">Short Fixedtest 555</data> </entity> + <entity name="CustomAttributeCategoryIsAnchor" type="custom_attribute"> + <data key="attribute_code">is_anchor</data> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml new file mode 100644 index 0000000000000..6184a220f047c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml @@ -0,0 +1,226 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="VerifyCategoryProductAndProductCategoryPartialReindexTest"> + <annotations> + <features value="Catalog"/> + <stories value="Product Categories Indexer"/> + <title value="Verify Category Product and Product Category partial reindex"/> + <description value="Verify that Merchant Developer can use console commands to perform partial reindex for Category Products, Product Categories, and Catalog Search"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11386"/> + <group value="catalog"/> + <group value="indexer"/> + </annotations> + <before> + <!-- Change "Category Products" and "Product Categories" indexers to "Update by Schedule" mode --> + <magentoCLI command="indexer:set-mode" arguments="schedule catalog_category_product catalog_product_category" stepKey="setIndexerMode"/> + + <!-- Create categories K, L, M, N with different nesting in the tree and Anchor = Yes/No--> + <!-- Category K is an anchor category --> + <createData entity="_defaultCategory" stepKey="categoryK"/> + <!-- Category L is a non-anchor subcategory of category K --> + <createData entity="SubCategoryNonAnchor" stepKey="categoryL"> + <requiredEntity createDataKey="categoryK"/> + </createData> + <!-- Category M is a subcategory of category L --> + <createData entity="SubCategoryWithParent" stepKey="categoryM"> + <requiredEntity createDataKey="categoryL"/> + </createData> + <!-- Category N is a subcategory of category K --> + <createData entity="SubCategoryWithParent" stepKey="categoryN"> + <requiredEntity createDataKey="categoryK"/> + </createData> + + <!-- Create different Products with different settings, assign to categories: --> + <!-- Product A in 0 categories, i.e. not assigned to any category --> + <createData entity="SimpleProduct2" stepKey="productA"/> + <!-- Product B in 1 category M --> + <createData entity="SimpleProduct3" stepKey="productB"> + <requiredEntity createDataKey="categoryM"/> + </createData> + <!-- Product C in 2 categories M and N --> + <createData entity="SimpleProduct2" stepKey="productC"/> + + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <actionGroup ref="AdminAssignProductToCategory" stepKey="assignCategoryNAndMToProductC"> + <argument name="productId" value="$$productC.id$$"/> + <argument name="categoryName" value="$$categoryN.name$$, $$categoryM.name$$"/> + </actionGroup> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <!-- Change "Category Products" and "Product Categories" indexers to "Update on Save" mode --> + <magentoCLI command="indexer:set-mode" arguments="realtime" stepKey="setRealtimeMode"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + + <!-- Delete data --> + <deleteData createDataKey="productA" stepKey="deleteProductA"/> + <deleteData createDataKey="productB" stepKey="deleteProductB"/> + <deleteData createDataKey="productC" stepKey="deleteProductC"/> + <deleteData createDataKey="categoryN" stepKey="deleteCategoryN"/> + <deleteData createDataKey="categoryM" stepKey="deleteCategoryM"/> + <deleteData createDataKey="categoryL" stepKey="deleteCategoryL"/> + <deleteData createDataKey="categoryK" stepKey="deleteCategoryK"/> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open categories K, L, M, N on Storefront --> + <!-- Category K contains only Products B & C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$)}}" stepKey="onCategoryK"/> + <see userInput="$$productB.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductBOnCategoryK"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductCOnCategoryK"/> + + <!-- Category L contains no Products --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$)}}" stepKey="onCategoryL"/> + <see userInput="We can't find products matching the selection." selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" stepKey="seeMessage"/> + <dontSeeElement selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontseeProducts"/> + + <!-- Category M contains only Products B & C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$/$$categoryM.custom_attributes[url_key]$$)}}" stepKey="onCategoryM"/> + <see userInput="$$productB.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductBOnCategoryM"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductCOnCategoryM"/> + + <!-- Category N contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryN.custom_attributes[url_key]$$)}}" stepKey="onCategoryN"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductCOnCategoryN"/> + + <!-- Open Products A, B, C to edit. Assign/unassign categories to/from them. Save changes --> + <!-- Assign category K to Product A --> + <actionGroup ref="AdminAssignProductToCategory" stepKey="assignCategoryK"> + <argument name="productId" value="$$productA.id$$"/> + <argument name="categoryName" value="$$categoryK.name$$"/> + </actionGroup> + + <!-- Unassign category M from Product B --> + <amOnPage url="{{AdminProductEditPage.url($$productB.id$$)}}" stepKey="amOnEditCategoryPageB"/> + <actionGroup ref="AdminUnassignCategoryOnProductAndSaveActionGroup" stepKey="unassignCategoryM"> + <argument name="categoryName" value="$$categoryM.name$$"/> + </actionGroup> + + <!-- Assign category L to Product C --> + <actionGroup ref="AdminAssignProductToCategory" stepKey="assignCategoryNAndM"> + <argument name="productId" value="$$productC.id$$"/> + <argument name="categoryName" value="$$categoryL.name$$"/> + </actionGroup> + + <!-- "One or more indexers are invalid. Make sure your Magento cron job is running." global warning message appears --> + <click selector="{{AdminSystemMessagesSection.systemMessagesDropdown}}" stepKey="openMessageSection"/> + <see userInput="One or more indexers are invalid. Make sure your Magento cron job is running." selector="{{AdminMessagesSection.warningMessage}}" stepKey="seeWarningMessage"/> + + <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are not applied yet --> + <!-- Category K contains only Products B & C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$)}}" stepKey="amOnCategoryK"/> + <see userInput="$$productB.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductBCategoryK"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductCCategoryK"/> + + <!-- Category L contains no Products --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$)}}" stepKey="amOnCategoryL"/> + <see userInput="We can't find products matching the selection." selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" stepKey="seeEmptyMessage"/> + <dontSeeElement selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontseeProduct"/> + + <!-- Category M contains only Products B & C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$/$$categoryM.custom_attributes[url_key]$$)}}" stepKey="amOnCategoryM"/> + <see userInput="$$productB.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductBCategoryM"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductCCategoryM"/> + + <!-- Category N contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryN.custom_attributes[url_key]$$)}}" stepKey="amOnCategoryN"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductInCategoryN"/> + + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCron"/> + <magentoCLI command="cron:run" stepKey="runCronAgain"/> + + <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> + <!-- Category K contains only Products A, C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$)}}" stepKey="storefrontCategoryK"/> + <see userInput="$$productA.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductAOnCategoryK"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryKWithProductC"/> + + <!-- Category L contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$)}}" stepKey="storefrontCategoryL"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryLWithProductC"/> + + <!-- Category M contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$/$$categoryM.custom_attributes[url_key]$$)}}" stepKey="storefrontCategoryM"/> + <waitForPageLoad stepKey="waitForStorefrontCategoryM"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryMAndProductC"/> + + <!-- Category N contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryN.custom_attributes[url_key]$$)}}" stepKey="storefrontCategoryN"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductCAndCategoryN"/> + + <!-- Open categories K, L, N to edit. Assign/unassign Products to/from them. Save changes --> + + <!-- Remove Product A assignment for category K --> + <amOnPage url="{{AdminProductEditPage.url($$productA.id$$)}}" stepKey="amOnEditProductPageA"/> + <actionGroup ref="AdminUnassignCategoryOnProductAndSaveActionGroup" stepKey="unassignCategoryK"> + <argument name="categoryName" value="$$categoryK.name$$"/> + </actionGroup> + + <!-- Remove Product C assignment for category L --> + <amOnPage url="{{AdminProductEditPage.url($$productC.id$$)}}" stepKey="amOnEditProductPageC"/> + <actionGroup ref="AdminUnassignCategoryOnProductAndSaveActionGroup" stepKey="unassignCategoryL"> + <argument name="categoryName" value="$$categoryL.name$$"/> + </actionGroup> + + <!-- Add Product B assignment for category N --> + <actionGroup ref="AdminAssignProductToCategory" stepKey="assignCategoryN"> + <argument name="productId" value="$$productB.id$$"/> + <argument name="categoryName" value="$$categoryN.name$$"/> + </actionGroup> + + <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are not applied yet --> + <!-- Category K contains only Products A, C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$)}}" stepKey="onStorefrontCategoryK"/> + <see userInput="$$productA.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductAWithCategoryK"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductC"/> + + <!-- Category L contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$)}}" stepKey="onStorefrontCategoryL"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryLAndProductC"/> + + <!-- Category M contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$/$$categoryM.custom_attributes[url_key]$$)}}" stepKey="onStorefrontCategoryM"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryMWithProductC"/> + + <!-- Category N contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryN.custom_attributes[url_key]$$)}}" stepKey="onStorefrontCategoryN"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="productCOnCategoryN"/> + + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="firstCronRun"/> + <magentoCLI command="cron:run" stepKey="secondCronRun"/> + + <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> + + <!-- Category K contains only Products B & C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$)}}" stepKey="onFrontendCategoryK"/> + <see userInput="$$productB.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="productBOnCategoryK"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="productCOnCategoryK"/> + + <!-- Category L contains no Products --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$)}}" stepKey="onFrontendCategoryL"/> + <see userInput="We can't find products matching the selection." selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" stepKey="noProductsMessage"/> + <dontSeeElement selector="{{StorefrontCategoryMainSection.productName}}" stepKey="dontSeeProductsOnCategoryL"/> + + <!-- Category M contains only Product C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryL.custom_attributes[url_key]$$/$$categoryM.custom_attributes[url_key]$$)}}" stepKey="onFrontendCategoryM"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeCategoryMPageAndProductC"/> + + <!-- Category N contains only Products B and C --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryK.custom_attributes[url_key]$$/$$categoryN.custom_attributes[url_key]$$)}}" stepKey="onFrontendCategoryN"/> + <see userInput="$$productB.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductBAndCategoryN"/> + <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductCCategoryN"/> + </test> +</tests> From 8cbc3743e5b0358bec65f3bc568ae23c39f338c7 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p-bystritsky@users.noreply.github.com> Date: Fri, 13 Sep 2019 13:34:26 +0300 Subject: [PATCH 782/841] magento/magento2#24340: Static test fix. --- app/code/Magento/Captcha/Observer/CheckUserEditObserver.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php b/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php index 70ccfeae6b7e7..872bbec4ffa56 100644 --- a/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php +++ b/app/code/Magento/Captcha/Observer/CheckUserEditObserver.php @@ -17,9 +17,6 @@ */ class CheckUserEditObserver implements ObserverInterface { - /** - * Form ID - */ const FORM_ID = 'user_edit'; /** From 2cfb105eb19f22c63bb1ec6185587b5c97e89f4c Mon Sep 17 00:00:00 2001 From: Nikita Shcherbatykh <nikita.shcherbatykh@transoftgroup.com> Date: Fri, 13 Sep 2019 14:18:11 +0300 Subject: [PATCH 783/841] MC-19737: Date type field saving incorrect value for a store in different timezone --- .../Model/Product/Option/Type/DateTest.php | 90 +++++++++++++++++-- 1 file changed, 84 insertions(+), 6 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/DateTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/DateTest.php index a6538423f37a1..8463577c34ed9 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/DateTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/DateTest.php @@ -6,13 +6,16 @@ namespace Magento\Catalog\Model\Product\Option\Type; +use Magento\Catalog\Model\Product\Option; +use Magento\Framework\DataObject; + /** - * Test for \Magento\Catalog\Model\Product\Option\Type\Date + * Test for customizable product option with "Date" type */ class DateTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Catalog\Model\Product\Option\Type\Date + * @var Date */ protected $model; @@ -28,12 +31,13 @@ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $this->model = $this->objectManager->create( - \Magento\Catalog\Model\Product\Option\Type\Date::class + Date::class ); } /** - * @covers \Magento\Catalog\Model\Product\Option\Type\Date::prepareOptionValueForRequest() + * Check if option value for request is the same as expected + * * @dataProvider prepareOptionValueForRequestDataProvider * @param array $optionValue * @param array $infoBuyRequest @@ -54,10 +58,10 @@ public function testPrepareOptionValueForRequest( /** @var \Magento\Quote\Model\Quote\Item $item */ $item = $this->objectManager->create(\Magento\Quote\Model\Quote\Item::class); $item->addOption($option); - /** @var \Magento\Catalog\Model\Product\Option|null $productOption */ + /** @var Option|null $productOption */ $productOption = $productOptionData ? $this->objectManager->create( - \Magento\Catalog\Model\Product\Option::class, + Option::class, ['data' => $productOptionData] ) : null; @@ -69,6 +73,8 @@ public function testPrepareOptionValueForRequest( } /** + * Data provider for testPrepareOptionValueForRequest + * * @return array */ public function prepareOptionValueForRequestDataProvider() @@ -109,4 +115,76 @@ public function prepareOptionValueForRequestDataProvider() ], ]; } + + /** + * Check date in prepareForCart method with javascript calendar and Asia/Singapore timezone + * + * @dataProvider testPrepareForCartDataProvider + * @param array $dateData + * @param array $productOptionData + * @param array $requestData + * @param string $expectedOptionValueForRequest + * @magentoConfigFixture current_store catalog/custom_options/use_calendar 1 + * @magentoConfigFixture current_store general/locale/timezone Asia/Singapore + */ + public function testPrepareForCart( + array $dateData, + array $productOptionData, + array $requestData, + string $expectedOptionValueForRequest + ) { + $this->model->setData($dateData); + /** @var Option|null $productOption */ + $productOption = $productOptionData + ? $this->objectManager->create( + Option::class, + ['data' => $productOptionData] + ) + : null; + $this->model->setOption($productOption); + $request = new DataObject(); + $request->setData($requestData); + $this->model->setRequest($request); + $actualOptionValueForRequest = $this->model->prepareForCart(); + $this->assertSame($expectedOptionValueForRequest, $actualOptionValueForRequest); + } + + /** + * Data provider for testPrepareForCart + * + * @return array + */ + public function testPrepareForCartDataProvider() + { + return [ + [ + // $dateData + [ + 'is_valid' => true, + 'user_value' => [ + 'date' => '09/30/2019', + 'year' => 0, + 'month' => 0, + 'day' => 0, + 'hour' => 0, + 'minute' => 0, + 'day_part' => '', + 'date_internal' => '' + ] + ], + // $productOptionData + ['id' => '11', 'value' => '{"qty":12}', 'type' => 'date'], + // $requestData + [ + 'options' => [ + [ + 'date' => '09/30/2019' + ] + ] + ], + // $expectedOptionValueForRequest + '2019-09-30 00:00:00' + ] + ]; + } } From 3d07b8ee783b70c1916c05a96f361ddeb9be7d63 Mon Sep 17 00:00:00 2001 From: rani-webkul <rani.priya4392webkul.com> Date: Fri, 13 Sep 2019 10:24:41 +0530 Subject: [PATCH 784/841] Fixed downloadable selected links price not added to product main price. #24579 --- .../Magento/Downloadable/view/frontend/web/js/downloadable.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js b/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js index 09a5ad1afa9ec..a1e8c785c696a 100644 --- a/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js +++ b/app/code/Magento/Downloadable/view/frontend/web/js/downloadable.js @@ -38,6 +38,8 @@ define([ }); } }); + + this._reloadPrice(); }, /** From 9ab3c9cb3a6e925854ee6847462656d246a870aa Mon Sep 17 00:00:00 2001 From: DianaRusin <rusind95@gmail.com> Date: Fri, 13 Sep 2019 15:50:24 +0300 Subject: [PATCH 785/841] MC-228: Customer should be able to sort bundle products by price when viewing products list --- ...torefrontSortBundleProductsByPriceTest.xml | 195 ++++++++++++++++++ ...rontCategoryPageSortProductActionGroup.xml | 23 +++ 2 files changed, 218 insertions(+) create mode 100644 app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml create mode 100644 app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml new file mode 100644 index 0000000000000..9a7a93d636ee0 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml @@ -0,0 +1,195 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontSortBundleProductsByPriceTest"> + <annotations> + <features value="Bundle"/> + <stories value="Bundle products list on Storefront"/> + <title value="Customer should be able to sort bundle products by price when viewing products list"/> + <description value="Customer should be able to sort bundle products by price when viewing products list"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-228"/> + <group value="bundle"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Create simple products for first bundle product --> + <createData entity="SimpleProduct2" stepKey="createFirstSimpleProduct"> + <field key="price">100.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="createSecondSimpleProduct"/> + + <!-- Create first bundle product --> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createFirstBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="firstProductBundleOption"> + <requiredEntity createDataKey="createFirstBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createFirstBundleLink"> + <requiredEntity createDataKey="createFirstBundleProduct"/> + <requiredEntity createDataKey="firstProductBundleOption"/> + <requiredEntity createDataKey="createFirstSimpleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createSecondBundleLink"> + <requiredEntity createDataKey="createFirstBundleProduct"/> + <requiredEntity createDataKey="firstProductBundleOption"/> + <requiredEntity createDataKey="createSecondSimpleProduct"/> + </createData> + + <!-- Create simple products for second bundle product --> + <createData entity="SimpleProduct2" stepKey="createFirstProduct"> + <field key="price">10.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="createSecondProduct"/> + + <!-- Create second bundle product --> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createSecondBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="secondProductBundleOption"> + <requiredEntity createDataKey="createSecondBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLinkFirst"> + <requiredEntity createDataKey="createSecondBundleProduct"/> + <requiredEntity createDataKey="secondProductBundleOption"/> + <requiredEntity createDataKey="createFirstProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLinkSecond"> + <requiredEntity createDataKey="createSecondBundleProduct"/> + <requiredEntity createDataKey="secondProductBundleOption"/> + <requiredEntity createDataKey="createSecondProduct"/> + </createData> + + <!-- Create simple products for third bundle product --> + <createData entity="SimpleProduct2" stepKey="createFirstProductForBundle"/> + <createData entity="SimpleProduct2" stepKey="createSecondProductForBundle"> + <field key="price">500.00</field> + </createData> + + <!-- Create third bundle product --> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createThirdBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="thirdProductBundleOption"> + <requiredEntity createDataKey="createThirdBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleFirstLink"> + <requiredEntity createDataKey="createThirdBundleProduct"/> + <requiredEntity createDataKey="thirdProductBundleOption"/> + <requiredEntity createDataKey="createFirstProductForBundle"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleSecondLink"> + <requiredEntity createDataKey="createThirdBundleProduct"/> + <requiredEntity createDataKey="thirdProductBundleOption"/> + <requiredEntity createDataKey="createSecondProductForBundle"/> + </createData> + + <!-- Perform CLI reindex --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <!-- Delete all created data --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createFirstSimpleProduct" stepKey="deleteFirstSimpleProduct"/> + <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> + <deleteData createDataKey="createFirstBundleProduct" stepKey="deleteFirstBundleProduct"/> + <deleteData createDataKey="createFirstProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="createSecondProduct" stepKey="deleteSecondProduct"/> + <deleteData createDataKey="createSecondBundleProduct" stepKey="deleteSecondBundleProduct"/> + <deleteData createDataKey="createFirstProductForBundle" stepKey="deleteFirstProductForBundle"/> + <deleteData createDataKey="createSecondProductForBundle" stepKey="deleteSecondProductForBundle"/> + <deleteData createDataKey="createThirdBundleProduct" stepKey="deleteThirdBundleProduct"/> + </after> + + <!-- Open created category on Storefront --> + <actionGroup ref="StorefrontGoToCategoryPageActionGroup" stepKey="openCategoryPage"> + <argument name="categoryName" value="$$createCategory.name$$"/> + </actionGroup> + + <!-- Asset first bundle products in category product grid --> + <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createFirstBundleProduct.name$$)}}" stepKey="assertFirstBundleProduct"/> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeFromForFirstBundleProduct"> + <argument name="selector" value="{{StorefrontCategoryProductSection.priceFromByProductId($$createFirstBundleProduct.id$$)}}"/> + <argument name="userInput" value="From $100.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeToForFirstBundleProduct"> + <argument name="selector" value="{{StorefrontCategoryProductSection.priceToByProductId($$createFirstBundleProduct.id$$)}}"/> + <argument name="userInput" value="To $123.00"/> + </actionGroup> + + <!-- Asset second bundle products in category product grid --> + <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSecondBundleProduct.name$$)}}" stepKey="assertSecondBundleProduct"/> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeFromForSecondBundleProduct"> + <argument name="selector" value="{{StorefrontCategoryProductSection.priceFromByProductId($$createSecondBundleProduct.id$$)}}"/> + <argument name="userInput" value="From $10.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeToForSecondBundleProduct"> + <argument name="selector" value="{{StorefrontCategoryProductSection.priceToByProductId($$createSecondBundleProduct.id$$)}}"/> + <argument name="userInput" value="To $123.00"/> + </actionGroup> + + <!-- Asset third bundle products in category product grid --> + <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createThirdBundleProduct.name$$)}}" stepKey="assertThirdBundleProduct"/> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeFromForThirdBundleProduct"> + <argument name="selector" value="{{StorefrontCategoryProductSection.priceFromByProductId($$createThirdBundleProduct.id$$)}}"/> + <argument name="userInput" value="From $123.00"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeToForThirdBundleProduct"> + <argument name="selector" value="{{StorefrontCategoryProductSection.priceToByProductId($$createThirdBundleProduct.id$$)}}"/> + <argument name="userInput" value="To $500.00"/> + </actionGroup> + + <!-- Switch category view to List mode --> + <actionGroup ref="StorefrontSwitchCategoryViewToListMode" stepKey="switchCategoryViewToListMode"/> + + <!-- Sort products By Price --> + <actionGroup ref="StorefrontCategoryPageSortProductActionGroup" stepKey="sortProductByPrice"/> + <!-- Set Ascending Direction --> + <actionGroup ref="StorefrontCategoryPageSortDirectionActionGroup" stepKey="setAscendingDirection"> + <argument name="directionSelector" value="{{StorefrontCategoryTopToolbarSection.sortDirectionAsc}}"/> + </actionGroup> + + <!-- Assert new products positions --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductFirstPosition"> + <argument name="selector" value="{{StorefrontCategoryMainSection.lineProductName('1')}}"/> + <argument name="userInput" value="$$createThirdBundleProduct.name$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductSecondPosition"> + <argument name="selector" value="{{StorefrontCategoryMainSection.lineProductName('2')}}"/> + <argument name="userInput" value="$$createFirstBundleProduct.name$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductThirdPosition"> + <argument name="selector" value="{{StorefrontCategoryMainSection.lineProductName('3')}}"/> + <argument name="userInput" value="$$createSecondBundleProduct.name$$"/> + </actionGroup> + + <!-- Set Descending Direction --> + <actionGroup ref="StorefrontCategoryPageSortDirectionActionGroup" stepKey="setDescendingDirection"> + <argument name="directionSelector" value="{{StorefrontCategoryTopToolbarSection.sortDirectionDesc}}"/> + </actionGroup> + + <!-- Assert new products positions --> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductNewFirstPosition"> + <argument name="selector" value="{{StorefrontCategoryMainSection.lineProductName('1')}}"/> + <argument name="userInput" value="$$createSecondBundleProduct.name$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductNewSecondPosition"> + <argument name="selector" value="{{StorefrontCategoryMainSection.lineProductName('2')}}"/> + <argument name="userInput" value="$$createFirstBundleProduct.name$$"/> + </actionGroup> + <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductNewThirdPosition"> + <argument name="selector" value="{{StorefrontCategoryMainSection.lineProductName('3')}}"/> + <argument name="userInput" value="$$createThirdBundleProduct.name$$"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml new file mode 100644 index 0000000000000..751b3b0d4536d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontCategoryPageSortProductActionGroup"> + <arguments> + <argument name="sortBy" type="string" defaultValue="Price"/> + </arguments> + <selectOption selector="{{StorefrontCategoryTopToolbarSection.sortByDropdown}}" userInput="{{sortBy}}" stepKey="selectSortByParameter"/> + </actionGroup> + <actionGroup name="StorefrontCategoryPageSortDirectionActionGroup"> + <arguments> + <argument name="directionSelector" type="string"/> + </arguments> + <click selector="{{directionSelector}}" stepKey="setDirection"/> + </actionGroup> +</actionGroups> From 922d4b084d0f1595833c6d0e64625e2372f5344b Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Fri, 13 Sep 2019 16:14:51 +0300 Subject: [PATCH 786/841] MC-11557: Check importing of configurable products with images present in filesystem --- .../Catalog/Test/Mftf/Data/CategoryData.xml | 5 +- .../Test/Mftf/Data/ImageContentData.xml | 4 +- .../Test/Mftf/Data/ProductAttributeData.xml | 19 +---- .../ProductAttributeMediaGalleryEntryData.xml | 10 +-- .../Mftf/Data/ProductAttributeOptionData.xml | 14 +--- .../Catalog/Test/Mftf/Data/ProductData.xml | 6 +- .../AdminOpenExportIndexPageActionGroup.xml | 15 ---- ...mportConfigurableProductWithImagesTest.xml | 74 +++++++++---------- .../AdminProductFormConfigurationsSection.xml | 4 +- .../export_import_configurable_product_2.csv | 2 - 10 files changed, 43 insertions(+), 110 deletions(-) delete mode 100644 app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml delete mode 100644 dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index bc013eedb0f4d..04b818076a47e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -118,10 +118,7 @@ <data key="include_in_menu">true</data> </entity> <!-- Category from file "export_import_configurable_product.csv" --> - <entity name="CategoryExportImport" type="category"> + <entity name="CategoryExportImport" extends="SimpleSubCategory" type="category"> <data key="name">CategoryExportImport</data> - <data key="name_lwr">categoryExportImport</data> - <data key="is_active">true</data> - <data key="include_in_menu">true</data> </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml index ac86deb671ed1..6e40499d0efeb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml @@ -18,9 +18,7 @@ <data key="type">image/png</data> <data key="name" unique="prefix">magento-logo.png</data> </entity> - <entity name="MagentoLogoImageContentExportImport" type="ImageContent"> - <data key="base64_encoded_data">iVBORw0KGgoAAAANSUhEUgAAAP8AAAEsCAYAAAAM1WX/AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAACY7SURBVHhe7Z0LlBxXnd6HXWyWhx9g8HS3pJFGPVU1klmC2RB28frBIbY00y2NpBnN9GMkG4JJcOJsgCVrszZg56wJuxj2BTjLY8FsADsb20kwmEc4WYgNxPYuiwX2yg9ZxrKsefS7R5IlufP/qm+N76hbo5qa7p6qru875zs6M5quvvd/76/q3lt1/9VDhU/7Ll79ikrauL66y8pVUsZ1ezf1nan+i6KoblV+bP22uUlrT+1dG2vlycEa/pWfH5kej4+oP6Eoqpt0aGx9vJqxvnF892Dt2JUbavmMNW/8fEx+X81aXy9MxNerj1AUFWQ9eGHPy4sp4wOHJ60ZXOWLWauW08CH8TN+r0YBM8W0+f47B3vOUIegKCpoyo/HL6tkzAcA9eFdgw3Qn2z8/5z8Hf6+krEeyE/EL1WHoigqCHo+2X9+MW18dm5y8MSLV25sCvrpfEI+V520TuA4h4b6etWhKYryqwTWXTJ3fxpXbyzone5qfyrjc+WsjALeLSeBrPl0MWNOqq+gKMpPmh3rf2Mpbdxbu2pj7ejuDZ6hP9k4Do6HEUQpY34zl+r/TfWVFEWtpH52ee+rZF7/kbmsVQb4hZPgbZVxXBx/btIqldLmjQeSkVeqIlAU1Wnl0gNJAf/nGOJXlzHEd2scH99jLwhmzX8sTgwkVFEoiuqEcjvW9pWzxleO7hqsHT/pnn2njO/F95cz5pefk/KoolEU1S5VM+Y1law1hatvqck9+04Z34vvr48CrEMolyoiRVGtVG5n/0Vylf07zLvd3LPvlFEOlAflkqnA/5kdX/92VWSKopajZ8Zir53LGrdWJ61jL161oW0Lest1fUFwQ60i5ZxLG7fuT/Sdq6pAUdRSVU4ZqXLGenK59+w7ZZTP2SxUzlpPlNPGhKoKRVFuND22zpQh9N+euHJD7YXdK7Ogt1yj3Ch/JWv97fSOPktVjaKoZsK++krGuK6cNUu4emKzTTOwgmJns5CcyIqVtPkH9zJvAEU1amZi3eXVjPkQYMHmGr8P8d0a9XA2C1Wz5oOFsXX/UlWZosKt6a1rotWs9XmsmGOY3AygbjHqd3hysFbNWH+1X+qtQkBR4VMpZbynOmkewFWxlO2eq/2pjPqhnvboJms+i/qrUFBUOFRI9f+WdP7v4tbYkRZuwgmKUV/UG/WXqcB3Cjvjb1Ghoaju1L6RtedUM8YtMsw/2s5NOEGxs1moOmkdKWeMjyM+KlQU1T3KTxjb57LWL+3V7wDcs++UEQfEoz4VsH6RT63fpkJGUcFWPXGm+V+PyzD35MSZ9EI7iUQrGetriJsKIUUFS0icmU8ZH0BCTFzVmiXOpBca8XGeDTictaaLqYH/gDiqkFKU/1VKxy+Zy1h24sxuumffKSNe9mYhiV81a92fG4tfokJLUf7UoZ19vcWM8ZnqMhJn0guNONqJRFPGXyIxqQo1RflHFSTOnLT24WoVhE04QTHiaG8Werf9bMBTchLYrUJOUSurfCr+5lLG+taLV25oaeJMeqER13oi0Q21Ytr4Vn7nwD9TTUBRndWeS899dTltfFSu9hXes++cnWcD5iTuiP/By3tfpZqEotqvysRAopOJM+mFRrz1RKKVlDGsmoai2qN64kzzS0d3r1ziTHqh0Q5HdtmJRP86t7N/jWoqimqdyinj31Wz1iFcbVYycSa90GgHJ5GotM/zTCRKtUzFifjvyBDfTpyJKwyh96fRLmgftBMSiRbT639bNSFFLU3PXBF7bSVdT5yJ3WfNOhztPzuJRKXdXqhkjE8ykSi1JBVSRqqStR7nPftgGu3lPBuAdkR7qqalqOY6OBrfKJ3l7iAnzqQXGu2IRUEZBdw1s3XNBtXUFFXXnrHBM2SIf305Y5ZxtecmnO4x2lFLJFpCO+8ZHDxDNT0VZs2MDlxezVh24sxqiBb0AASemMO/zf6/24x2RfvW7woMPjjDRKLh1dT2NZG5tHkbEkqeCNEmHEAA6Oek3vm0+Rh2z4XtsWS0t51INGv+F2zGUl2CCoNKE8bV1UkrNIkzYdSxrJJnYgNSLjWQrSUjr5wZj++qZq2n8Xv8f1hi8VIiUetXTCQaAk3vjL9FzvahTJyJ9/xVZIg/lzH//Int/W9QIbF1UH6uZI2/kJOA/XfNPt+NRvs7iUQlLt/Jj8cvVCGhukUzm/vPKmaMj8sV70iYNuGgc9svzJA6y0nv/unR+KUqJE01PR6/rDpp3m9fEUO0/uFsFqpI/yimjFv2Sn9RIaGCrPzEeiTO/AU6dFgSZ6KO8yvcGTNXThkfuGN7z6+pkCyqj17c8+tyovygjBJy+HxY7nygjkwk2iWa2rbOqGSsO5AQMmyJM+uJMDfgzTjfOLTFWyLMqR39A3LiuBPHCWf87ESi30AcVEgovwsJH4sTxgfDljhTv3LJVfuX+fTADhWSZamUGhiVk8Cj9nFDOHJCP5KpwAd+ICMiFRLKj8qND1wcxsSZzpy1nDXx8otbprec8xoVkpZoZvPZZ1UzxsflpBLONZN32W8Wuh/9S4WE8ouwWl1MG39RnbSOvyids1lDdqPROZ3VagHzu+1+7RVeM1bJGN8L590S3CIdPI5+hv6mQkKtpJDQ8XDIEmeijs596nLGPFDp8H1qGQVcXZXvxfeH6jkJNa2qZq19chLYpcJBdVpI4FhKG6FMnGm/6lqGo5W0eRte8a1C0lHNjK2OyRTjr7CHvttfLa4b/cxJJCr9716ZCrxJhYRqtw4kI68sp4yPze2y7E04YZp/Os+mlzLmQ4Xx+BUqJCuqQmr9JinPw/YVMSQjL9hZZ5nLWmUkEkW/VCGh2qFieiCJhI2h62hq5bmaNYtytf+DvZt6zlQh8YX2buo7s5IxrkP57BOylLdZPbrN9glZTQWQ0LWYGUiokFCtUn5i7dpqxrr9qFz5wpQ4E50L+9ExrC6nzXtmR+MbVUh8qdlUfGM5Y/4PJx9CWE7OMPol+mc5bX0lP7J2rQoJtRzJvPLasCXORB21xaXHZVg5ocIRCMm0DJmQnkAmnXAtwjojNOsQEr6qcFBLVXE8/rZKxrITZ2JxKyxXEWcuiRx0hbRxq8wlz1MhCZRQ7lLa+BTqgfo0q2s3Gv3Ufsmo1BmJRGfHmEjUtZBwsZoxPilXjtB1Gjv7rP1AidU1nWYW2Y8n5SQu9QrfSdx+/uKFatb8k30ja89RIaGaqTAxkLaHizJ0CuNw8fCkdbCcMq+pqXh0i27o6XlZQYbBc1nr+bBO36RfM5FoM81MGIMSnLvCmDgTC0Uv7FZvnMms7VMh6Uqphdsvo75hWriFnYXbSsb879M7DEuFJLy6F7eI0sb1SKyIs2OYNuE4t4jkavgPsyF711xR6iv1/hnqH5Zbtqijs1mojESiGeM69H8VknBpdjx+hcyFHrQBCNtc0O70VjXMb5nFW47lJHBTNWvNIR5helirvlkIdwXMB5FAVoWk+4XEmdWM+QUs/oT1sVC56n0zlzJ+U4Uk1Do0PvAmice9oX1MW0Y+1bT1eXChQtKdKqSN91YnQ7ghRG3Ckavc0/kJ40oVDkpTfnzgKonPfsQpnIlEzWcLE8bVKhzdI2zCkcp9J6yJM6VT42r/508m+89XIaGa6HmJTyVj/aVMicKbSHTSvC8/Gn+zCklw9eTYurPLKeMW6fyHcc8+TPM650EPmdfdPzUWv0SFhHKh6Yn4pQJBaJOyVLODh8tp84/AjwpJsITEmQL9L9GAoUycmbVmkTiT6Z+8CXGrMJFosBKJHhpbH0fiyOOhTpxpfu35HX39KiTUMnRoIr5eLiJfC3MiUan/1wsSBxUS/8lOnClXusMhT5xZmjC2q5BQLRQSkoY+kWjafD84UyHxh/Lj8cukYew5Whg34eBlD9WMccujLU6cSS3U9BbzNSqR6NGwrSFpzwbcn59Y/OUrHRFWZ4tp47Nzk4MnXgzZyy6d1VkZkn13Zqz/rSokVAdUSPe/VaaW9USiu0J290g4q05aJ8op4zPgT4Wks0ICQ+n49Rc8hmgYNp84M2s9V8p04X3ZAKmUNt4ro4CDaI/QJRJ9tz0KeLqYMSdVONqv2bH+NyJxIYZdYU2cicSV0ylzRRJnUgs1M2bEymnj8+F9YtTO6fjNXKq/fU+M/uzy3lfJvP4jc1mrHNb5VhmJMydC9Cx2gFRIx6+Q9rETiYbx2YC5SatUSps3tjyRaA6JMzPmzxHYMCXOdFZaZY5VkCH+dX5LnEkt1A8uXv0KGZVeLyAU0G5ov2bt2m0Gj/OJRLPmPxYnWpBINLdjbV85a3wlbIkz4fnEmRnjLuQbUCGhAiC0l0wF7kb7hTE/hJ1INGN++TnhV4VkaapmzGsqWWsKZ5PQJs6cMMdVOKgAqpA2JtCOaM8wZoYSfg+BYxWO0yu3s/8iOWuEMnFm/X1s1jEZOt5aGIu9VoWECrCeHYm+TqZsn0a7on3DtFbl7C+xE4mOr3+7CkmjnpHOPpc1bq0HaUP4gvQupFiy/m7RIFGBVS5tXlS1s0GHNJGocD0nFzUkyFUhqQt54csZ68nwDY/qQ/w5GR4VJox/i0STKiRUF+qOnp5fU4lE7fdAhOkRdGc6W85aT8y/ByI3MXDHcRkahHJhROosQ/yvYGHTDgYVCiGRqEwFbg9lIlGpL3gH9z3Ht6+pFXbG6/8pV8KT/7ibjDOgljjzZ8VxY0j1ByqEQuJU9AP0h1DcwlZ8g3dw3zM7FKnZ3rq6lhuXkwD+oAtPAvbcB42ctSphTpxJLdSesXNfXcZbnyetStc+vKaYBt/g3GG+Z3Y4WrPtnAS29dXyKUN9oMmBAmaczecTZ05a/2t2vP8C1e4UNS88ti6jgO5KJAp+wbHwDK7nGVfMvwS/Y/Wfue1ra/m0Wf9wswP73PoiR3XS2ldMx3epdqaoU6qYMnZ3xYY1cCv8gmOda92N8DvGHyditdxo/0sHO/kLfOwT9tbHwVolY3zmudH1r1dtS1GnFfpLJWt9Zk76T+A2CylOwS34bQa941PDD+OD8JZVtRwWBXFgH58EcJaef7AhYz4wvSN+mWpPilqypsf63yH96MeBeOBNsQlOwes8u824Vl4cfsfqQLmta2r58QHfnQTQKM4mHDlj54oZ44NMnEm1QrchPV3a+H3pV3n0L989G+CwKFyCTzfQO3YHv2N10Ny2tbWcsyjYrEAd9nzizKz5tUPbfJzokAqs7MS0WevreC7AN4lEhT9wCB51Pt16afA7xpdgPWDHuhVbFMTZ10mcKY3CxJlUR2QnEp1c4USi4A2LecLf6eb1i9kb/I7xpUk5CYytf6lQJxe0Da4/r2xDf1iu9n/ExJlUJ7V389lnlTPGx/FyjY4+G6D4Am/gziv0jpcHP4wCwFva/5AQzrK4B2tvVMia353eGX+Lag+K6rimR/t/q+IkEm3nswGKKfshHZeLeW68fPgd2wWSf0fW1PITrV0URFDnE2dmzAOllPEeFf/QSaY3g7WxwTPUj77Q3k19Z6Jc6sfQqZoxrm7LC2gdhoQncGXz1QLoHbcOfseqcDn7ScHWrAfgXuuRXYO1ctr6/MzY6piKeah0aGdfbylrfTmXMp6ckWGn+rUvlHvnWWdLuZ4qZ80voZzq16HSzLboKrkwfQH9tCXPBoAbezGvbwFXrXTr4XeMwi5jURBnTydxZiljPlwYj1+h4hw6VVLWu8oZ61e1q9+IWD7+zGjsN9R/+UJIGjmbMp9C+UpSzkp64Cr1X6FTJR2/opQ2/x791lMiUXDSgsU8N24f/I5R+OSqJS0KOvfs5yatfCVtXI/EjCq2odLUePzCatb8NuaUWOuA82njsZZnaF2msEkqlzL3Yt7rrMlUs9a9U93wqmkP2if9Vfrth5eUSFRxUV/MU/P6Zjy10O2HH0ZFxDkXi4J64sywziORJr2aMf9T5aRXmwcBfpTTuRtTxt2YtHmz38rbKc2OxjeeNpGoYgFcgA+HlaYctdidgd+xU7GRNbXchLNzsD400l52+bjMnUKbOLM0Ed9azVh77FicdB85KPDDepvKKOCRXCq+Rf156FRIGSmJweN4y878ZiGn7wsH9cW8zkHvuLPwO1aVxI4j6TS1Oek0MkQ6WkoZn0TCRRWzUCk/tmadDPH/BleIUz1BFiT4daM+yJojo5mv5kfWrlUfC5Wkzc5DYlj0c/R39PvFdtx1wisDv2OpdHnLqlppbN3B2XEjlIkzsQehOGG+v5I1T/tq86DCj/q8tPfCnC6nzN9DPj318VAJiUQr0t/R71cKescrC7/4ha2x2vRQ5CcqNqGSDPl+V672/xdQuNk1FlT4HaN+zq5LGQb/KLfTvEgdIlSaHer9Kfp9Mx466RWH/6gEYWY48pCKSyj0mAwBKxnzzypZ6wTSpDcDpZmDDr9u5NAvS/0rafNPwzTVq4lnhyMPo98346GTJvwdVmFiIF2dtJ7A1X6pmWK6CX7U28m0JPPgx2dTRkodrqtF+DWHBX7kDpzLmPcgRxwW9ZYCveNugt8x4uDc3pXR0D2zqfhGddiuFOHX3O3wA9RCeuBGmeOWcZUruHng4xTuRvgdIy6IT3XSLEm8bnjIZ3VslQi/5m6Gf3Zi/WYZ0tqPerYiL3w3ww8jPtp7FR6eHVu/SX1F14jwa+5G+PFq5HLG/GKrX23e7fDrRtywSaYkcczt7F+jvirwIvyauw3+UnrgfXLVeg5Xr5Zu7xSHCX7Ebf5dipPWgVJq4F+rrwu0CL/mboFfhqi/Xc2a/9tO7LCrPYkdwgS/Y8QR8bQ3C2Ws7xfH429TXxtIEX7NQYd/38jac0pp84/l6nSk3Smdwgi/4/nUbRLnUsr8xJNj685WXx8oEX7NQYa/PGGNVTuYzDHM8MOIr7ZZ6JcyFRhVRQiMCL/mIMI/vcOwyhnzv9lpnNvc4XWHHX7d2CyE+JfT5p3TY5apiuJ7EX7NQYL/wQt7Xl7JmB+Sq878Cxyadcx2mfAvtLNZSNojV0kbv4/2UUXyrQi/5qDAXxg33ilX+/9nrz57Sc/UAhP+RqMdnHRv5Yz103LKfIcqli9F+DX7Hf79W9dEK1nzs/ZLG5ewCacdJvyLG5ukqpPWCbyc9entayKqeL4S4dfsZ/grafNflbPms7iqtPqevRcT/sWN9nGeDShlzGdlivZuVUTfiPBr9iP8eBmInjhzpaF3TPjdGe2FWKlnA76FRKiqqCsuwq/ZT/Dnd7/pHJnXNyTO9IsJ/9LsPBsg7TlXTps3o31VkVdMhF+zn+DP7ej/cO09F9SqbXpCb7km/Es32hHtiXadGe2/XhV5xUT4NfsJ/unNvTfXRvtquZ2dffGoWxP+JVq1H3Lh10bX1KR9b1JFXjERfs1+gn92KPrR2og0ylCkltu6ppYbb+07B5drwu/Sqs3Qfrmt9Vz4aFfpZx9RRV4xEX7NvoN/26p62VRmVbwrLZdy3jHQpKN10IT/NEb7APoJo5YbWfiOO7Qr4V9owq9pAfyOnZPAdm/vHGylCf8iRrvgHXfSTnq7OSb8jSb8mprC7xidCS8eHe1/qbOd3AHbbMLfxKod0C6LvdiS8Dea8GtaFH4YHQvesqq+KKiGmQ0dsk0m/JoRd4k/2gHtMd82zdpNTPgbTfg1nRZ+x05H27pa5pedWxQk/GIVa8Qd8T8d9I4Jf6MJvybX8DtWnQ6LgnlnUbBZh22RQw8/4itxRrz1+Lsx4W804de0ZPgdOyeBHe1dFAwt/IgnFvMkvnq8l2LC32jCr8kz/I7RKZOxWm6sPYuCoYNfxQ/xRFy9QO+Y8Dea8GtaNvwwOii8ZXUttzNe78AtOgmEBn4VM8QPcZyPabN4uzThbzTh19QS+B2rDpsbWWM/dNKKk0DXw+9APzFgx60V0Dsm/I0m/JpaCr9j1Xlz29Yue1Gwq+FHXOzFvLUL4tYqE/5GE35NbYHfsXMSsBcFVWdvBsEi7kr4EQeJx3IW89yY8Dea8GtqK/yO0bmTq+ydZvOd/2QgTuGugl/VG3FAPNoFvWPC32jCr6kj8MPo6DAWBcfdLwp2Bfyqrqh3qxbz3JjwN5rwa+oY/I5Vx8fiVn5+UbAJMMqBht95FNrecdfaxTw3JvyNJvyaOg6/YwVBbjsWBU/9kFBg4Ud9pF6on17fTprwN5rwa1ox+B0DikV2DgYOflX+3Oi6RXfcdcKEv9GEX9OKww8PwQIJFgXxkJAGUWDgd6DHQzrOYh7q1ay+HTLhbzTh1+QL+B3bwIixc1AtCh69cqO/4Uf5pJz2Yt4Sdtx1woS/0YRfk6/gd+wAtK2vdnQSw2nznwCbKrIvdOjSc1+dy1iPH8ladjn9BL1jwt9owq/Jl/A7FpiObJWpwLa+R2pjg2eoIvtCezf1nZnbtmbPYSmf36B3TPgbTfg1+Rp+sR2rod4HVXF9pdmh3of80KFPZcLfaMKvKRDw+yRWuvzUoU9lwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt9owq+J8HsT4Xcvwq+Z8Ls34fduwt/onmNbV9UKiagEpvkftNuE370Jv3cT/rrBOXgH9z0zQ9HPVZKx2gsrVBjC796E37sJf93gHLyDe7tAUphNpUT04drIqloZ/9HkQ+0y4Xdvwu/dYYYfPINr8F1KxB4C7/XSKO27ePUrSsnYHxYT0fKL8kd59aFmB2ulCb97E37vDiP84DcvQ3zwLBf3Umk48uEfCOf1kjTRbKL3AjlL3I3hwZEt7S8g4Xdvwu/dYYQf/ILjciJ218Et52+sl8CFColoRkYB+xA0+dc+izT7guWa8Ls34ffusMAPTsGr4vap2WQ0Xf/mJepAMnKeTAU+XU5GXzjepsISfvcm/N4dBvhz4uMjsVpJeBVuP/WY8Fv/1mUol4xcVEzEfoS5Q7XFC4KE370Jv3d3M/zgsSpDfPBZTER+ODsUeXv921qkG3p6XiYjgGtl/jCFQGIhoRUnAcLv3oTfu7sRfvAHDlE34fJQcTh6LTitf1MbdHBL77ricOyrWExoRQUIv3sTfu/uRvidRflSInp7ftMb1ta/oQMqJqJbxHvss84ypgKE370Jv3d3C/zgzL5nL/UR/h4pJqPJ+pE7rL2bzz6rOBS9uZKIVU7IfAMLDs0KvJgJv3sTfu/uBvjBF+b1MsSvCPgf23Ppua+uH3UFld8ce3N+OHLfiZFY7bAMQ5YyCiD87k34vTvo8IMrrOTLHP9bU8Jb/Wg+Un4odlUlGd2vhiSuTgKE370Jv3cHEX7wA45Q9nIiuj+XiO2uH8Wnmtq0JiKjgNuweQA7h5pVSjfhd2/C791BhB/8gKPcUPRzh4bO660fIQDKD/W+o5yM/gRBt3cRNakcTPjdm/B7d1DgByfgxd5kl4j+OD98/mX1TwZMezf1nSlzlA9JJXIIfrNnAwi/exN+7w4C/M49e5k6z+aHYx/cMzZ4Rv1TAdb0pqgpo4A7m20WIvzuTfi92+/wH1XPzQgnd0wNRY36X3eR8oneUTmrPYaGKCXrowDC796E37v9CD/6f0nds5fR8aP5ZGRH/a+6VPsTfeeWEtE/LiaiR/FsAEYDhN+dCb93+w1+9Hv0f2HhSCER/cTPhYv6X4RAM1esfmslEf1+bXR1TaD7hfr1iorwexPhX5okVo+i30v//9705t5/rn4dLqHTlJOR908PR+6rXX3hy+u/XTkRfm8i/O5VGxs8Q2J1nwzzf0/9Ktya3nLOa2oX9/y6+nHFRPi9ifC7F/o5+rv6kfKLCL83EX4q8CL83kT4qcCL8HsT4acCL8LvTYSfCrwIvzcRfirwIvzeRPipwIvwexPhpwIvwu9NhJ8KvAi/NxF+KvAi/N5E+KnAi/B7E+GnAi/C702Enwq8CL83EX4q8CL83kT4qcDL7/Aj+8v0cPQnqri+kpTvpyjfyWX2iwk/taj8Dj/e0VZIRA9ODUcuVkX2hXKbey8pSrlQvmbl9oMJP7Wo/A6/k9O9lIgeKyVif3rgksh5qugrouc29b5eyvFnUp7ji72bwQ8m/NSi8jv8MABzXuEk0D1ZHI5kVfE7qmIyMinf/xTK4fbVbCtpwk8tqunN0Zv8Dr9uvAPhmMyzy8noPQcv771AVaOten7oDW8sJ6L/89hI4zsY/Gy7XYd6P6aqQVELJVf+j9XGVmNe7fsrmWPntc1yFS6XEpEb9l28+hWqOi3VA2+L/UZpOHKjjDQq+D4vr2NfCaMd0Z5oV175qVNqZnh1TK6iX6jKFc3PK9cnGx0ci232VCAZ/fupzb1Dqkot0dRw77Ac9x9wfHxPUE6MMNqxIu1ZGI58YXrrmqiqEkU11+xwZFM5EXsoqJ29KmWWofkXf7U5ukpVyZOevSK6Wo7zpSCfDNGOU0ORzapKFHV64SWjpUTshqIMpwM5zMUoIBE9WBiOvU9VaUnKDcWukc8/j+MEaRqUF6O90G6lZOQP0Y6qShS1NB1M9F5QTMTuxsLa4QAtcAHWOSmvWg/4/kxi9VtVlRbVzPCqf1FJRr+PV0nh80Ea9WABEiMUmaLcM7vp/I2qShS1POGWmlxNAnNryzFGLOqdcEcLw9E/2XNF7LWqSgv0jPy+JP+Pv8PfB2mkg/ZQ7fJkLhHNqCpRVOt0IBk5T4aSn5J58LHjI8EaBeCtyACkkog+NpvoHVNVslUY7t0pV/t/sqcK6u3JzY7jN+MEhXYoJ6MvlJKxTz/7zujrVJUoqj2Sq8vvlhORH9bkConFtSAOjWeGIt+Y2Ry5fHYoegd+DtI9e8QbcX9RwC9KO+SSkYtU01BU+1W7oedlxWT02nIiNhXERTEAjxVx/Iufg1B2lNFZzETci0PRf3+DtINqEorqrA5tfv364nDsq1gM9POW1mbOC0jNfu9X40SFOMsJ4PbnE2/oV01AUSsrmQpsKSVij9TnzcGaCvjZiKNzz76SjP08NxzZqkJOUf4RXr0sV9Oby4loNUgr5n414odblTLEr8jU5KZHf4evtqZ8rvzm2JuLyci3j0vHxTCVo4ClGfFC3BC/4nDk24inCi1FBUP5odhVMgrYjyFrkJ4NWCkjPs49e7naP50f7r1ShZKigqepTa+L5Iain0Oyi2NyJWvW6em6ER+Z35/IS7yelripEFJUsJXfsuod5WT0x3g2wO9ZbzppxAHxQFxklPRAfvj8y1TIKKp7hE0m+eHof5ROnsNCVtButbXaqL+9oJeMzs4moh/aMzZ4hgoVRXWnnt0UNYvJ6J32k3UBezagVcYzEbDM8e+YGooaKjQUFQ4VEr1jlWT00fqzAd2/IIj6OXsM5Gr/aH5z76gKBUWFT/sTfeeWEtFPBG033VKNejm7C4vJ2H9GvVUIKCrcOhjgffSLGfVAfVCvaiL6Pbd5BSgqdJpN9P4bGRo/h6FxkDYLnWyU29mEI/U5UBqOvFdVkaKoUwm592R4/EVcMbEo2AwuvxvlRvmREPWZ4dUxVTWKotxodiiyWU4CD9v3wAPwbADK52zCKSViDyERqqoKRVFLFfLwF4ajNxZ9nEgU0DuJM+VkVSokIjf8oE3vD6Co0Gk20XuBXFXvxiOwfsu6g/LYj+YmYncd3MLEmRTVFqlEovswtF7JzUL4XmcTjvz7VIGJMymq/aonEo19WubVx4+vwIIgph524sxE7JiU41OPSXlU0SiK6oRyw5GLi4nYjzDX7kQiURy/njgTV/vID0tMnElRK6cbenpeVtYSiWKzTKtPAjgejovj43uKw9Fr8b2qCBRFraScRKJYfGt1IlF7A5Ict5SI3l5g4kyK8qeKiegW8R77Kr2MqQA+59yzl+M9UkxGk+orKIryq/ZuPvus4lD05koiVvGyWcjZhCND/IoM8W96dAsTZ1JUoITEl/nhyH0nRur57t2MAuqJM/Eij8i3p5g4k6KCLSQSrSRPnUgUP+P39QW96P5iIrZbfZSiqKBratOaiFzNb7MTiW5dmEgUP+P3SDR6aOi8XvURiqK6SfmhXiQS/Qnu1QN4/CtX+x8zcSZFhUC4R19I9l5THYk9kRuOvE/9mgqVenr+P+OvTjWo+kMRAAAAAElFTkSuQmCC</data> - <data key="type">image/png</data> + <entity name="MagentoLogoImageContentExportImport" extends="MagentoLogoImageContent" type="ImageContent"> <data key="name">magento-logo.png</data> </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index 0bdc3a261053f..1986821f899cf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -377,25 +377,8 @@ <data key="attribute_code" unique="suffix">size_attr</data> </entity> <!-- Product attribute from file "export_import_configurable_product.csv" --> - <entity name="productAttributeWithTwoOptionsForExportImport" type="ProductAttribute"> + <entity name="ProductAttributeWithTwoOptionsForExportImport" extends="productAttributeDropdownTwoOptions" type="ProductAttribute"> <data key="attribute_code">attribute</data> - <data key="frontend_input">select</data> - <data key="scope">global</data> - <data key="is_required">false</data> - <data key="is_unique">false</data> - <data key="is_searchable">true</data> - <data key="is_visible">true</data> - <data key="is_visible_in_advanced_search">true</data> - <data key="is_visible_on_front">true</data> - <data key="is_filterable">true</data> - <data key="is_filterable_in_search">true</data> - <data key="used_in_product_listing">true</data> - <data key="is_used_for_promo_rules">true</data> - <data key="is_comparable">true</data> - <data key="is_used_in_grid">true</data> - <data key="is_visible_in_grid">true</data> - <data key="is_filterable_in_grid">true</data> - <data key="used_for_sort_by">true</data> <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabelForExportImport</requiredEntity> </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml index 7d7f2cb7bf4be..75b4ef773a934 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml @@ -31,16 +31,8 @@ <requiredEntity type="ImageContent">MagentoLogoImageContent</requiredEntity> </entity> <!-- From file "export_import_configurable_product.csv" --> - <entity name="ApiProductAttributeMediaGalleryForExportImport" type="ProductAttributeMediaGalleryEntry"> - <data key="media_type">image</data> + <entity name="ApiProductAttributeMediaGalleryForExportImport" extends="ApiProductAttributeMediaGalleryEntryTestImage" type="ProductAttributeMediaGalleryEntry"> <data key="label">Magento Logo</data> - <data key="position">1</data> - <array key="types"> - <item>image</item> - <item>small_image</item> - <item>thumbnail</item> - </array> - <data key="disabled">false</data> <requiredEntity type="ImageContent">MagentoLogoImageContentExportImport</requiredEntity> </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml index 8daa63f08d325..bb0e85bcbb40b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml @@ -87,20 +87,10 @@ <data key="value" unique="suffix">white</data> </entity> <!-- Product attribute options from file "export_import_configurable_product.csv" --> - <entity name="productAttributeOptionOneForExportImport" type="ProductAttributeOption"> - <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <entity name="ProductAttributeOptionOneForExportImport" extends="productAttributeOption1" type="ProductAttributeOption"> <data key="label">option1</data> - <data key="is_default">false</data> - <data key="sort_order">0</data> - <requiredEntity type="StoreLabel">Option1Store0</requiredEntity> - <requiredEntity type="StoreLabel">Option1Store1</requiredEntity> </entity> - <entity name="productAttributeOptionTwoForExportImport" type="ProductAttributeOption"> - <var key="attribute_code" entityKey="attribute_code" entityType="ProductAttribute"/> + <entity name="ProductAttributeOptionTwoForExportImport" extends="productAttributeOption2" type="ProductAttributeOption"> <data key="label">option2</data> - <data key="is_default">true</data> - <data key="sort_order">1</data> - <requiredEntity type="StoreLabel">Option2Store0</requiredEntity> - <requiredEntity type="StoreLabel">Option2Store1</requiredEntity> </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index faad9094b8d7f..25e3e39b20d99 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -351,14 +351,10 @@ <data key="filename">adobe-base</data> <data key="file_extension">jpg</data> </entity> - <entity name="TestImage" type="image"> + <entity name="TestImage" extends="TestImageAdobe" type="image"> <data key="title" unique="suffix">test_image</data> - <data key="price">1.00</data> - <data key="file_type">Upload File</data> - <data key="shareable">Yes</data> <data key="file">test_image.jpg</data> <data key="filename">test_image</data> - <data key="file_extension">jpg</data> </entity> <entity name="ProductWithUnicode" type="product"> <data key="sku" unique="suffix">霁产品</data> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml deleted file mode 100644 index 83762d347a93f..0000000000000 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminOpenExportIndexPageActionGroup.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminOpenExportIndexPageActionGroup"> - <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="goToExportIndexPage"/> - <waitForPageLoad stepKey="waitForExportIndexPageLoad"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index 7548c09912897..0124ff5a16dda 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -16,7 +16,7 @@ <description value="Check importing of configurable products with images present in filesystem"/> <severity value="CRITICAL"/> <testCaseId value="MC-11557"/> - <group value="ConfigurableProduct"/> + <group value="configurableproduct"/> </annotations> <before> <!-- Create sample data: @@ -27,10 +27,10 @@ <!-- 2. Create Downloadable product --> <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> - <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink"> + <createData entity="ApiDownloadableLink" stepKey="addFirstDownloadableLink"> <requiredEntity createDataKey="createDownloadableProduct"/> </createData> - <createData entity="ApiDownloadableLink" stepKey="addDownloadableLink1"> + <createData entity="ApiDownloadableLink" stepKey="addSecondDownloadableLink"> <requiredEntity createDataKey="createDownloadableProduct"/> </createData> @@ -43,54 +43,54 @@ <!-- 4. Create configurable product with images --> <createData entity="CategoryExportImport" stepKey="createExportImportCategory"/> - <createData entity="ApiConfigurableExportImportProduct" stepKey="createConfigProduct"> + <createData entity="ApiConfigurableExportImportProduct" stepKey="createExportImportConfigurableProduct"> <requiredEntity createDataKey="createExportImportCategory"/> </createData> <createData entity="ApiProductAttributeMediaGalleryForExportImport" stepKey="createConfigurableProductWithImage"> - <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createExportImportConfigurableProduct"/> </createData> - <createData entity="productAttributeWithTwoOptionsForExportImport" stepKey="createConfigProductAttribute"/> - <createData entity="productAttributeOptionOneForExportImport" stepKey="createConfigProductAttributeFirstOption"> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <createData entity="ProductAttributeWithTwoOptionsForExportImport" stepKey="createExportImportConfigurableProductAttribute"/> + <createData entity="ProductAttributeOptionOneForExportImport" stepKey="createExportImportConfigurableProductAttributeFirstOption"> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> </createData> - <createData entity="productAttributeOptionTwoForExportImport" stepKey="createConfigProductAttributeSecondOption"> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <createData entity="ProductAttributeOptionTwoForExportImport" stepKey="createExportImportConfigurableProductAttributeSecondOption"> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> </createData> <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> </createData> <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeFirstOption"> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> </getData> <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeSecondOption"> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> </getData> <createData entity="ApiSimpleOneExportImport" stepKey="createConfigFirstChildProduct"> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> <requiredEntity createDataKey="getConfigAttributeFirstOption"/> </createData> - <createData entity="ApiProductAttributeMediaGalleryForExportImport" stepKey="createSimpleProduct1Image"> + <createData entity="ApiProductAttributeMediaGalleryForExportImport" stepKey="addImageForFirstSimpleProduct"> <requiredEntity createDataKey="createConfigFirstChildProduct"/> </createData> <createData entity="ApiSimpleTwoExportImport" stepKey="createConfigSecondChildProduct"> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> <requiredEntity createDataKey="getConfigAttributeSecondOption"/> </createData> - <createData entity="ApiProductAttributeMediaGalleryEntryTestImage" stepKey="createSimpleProduct1Image1"> + <createData entity="ApiProductAttributeMediaGalleryEntryTestImage" stepKey="addImageForSecondSimpleProduct"> <requiredEntity createDataKey="createConfigSecondChildProduct"/> </createData> - <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductTwoOption"> - <requiredEntity createDataKey="createConfigProduct"/> - <requiredEntity createDataKey="createConfigProductAttribute"/> + <createData entity="ConfigurableProductTwoOptions" stepKey="createExportImportConfigurableProductTwoOption"> + <requiredEntity createDataKey="createExportImportConfigurableProduct"/> + <requiredEntity createDataKey="createExportImportConfigurableProductAttribute"/> <requiredEntity createDataKey="getConfigAttributeFirstOption"/> <requiredEntity createDataKey="getConfigAttributeSecondOption"/> </createData> - <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> - <requiredEntity createDataKey="createConfigProduct"/> + <createData entity="ConfigurableProductAddChild" stepKey="addFirstExportImportConfigurableProductChild"> + <requiredEntity createDataKey="createExportImportConfigurableProduct"/> <requiredEntity createDataKey="createConfigFirstChildProduct"/> </createData> - <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> - <requiredEntity createDataKey="createConfigProduct"/> + <createData entity="ConfigurableProductAddChild" stepKey="addSecondExportImportConfigurableProductChild"> + <requiredEntity createDataKey="createExportImportConfigurableProduct"/> <requiredEntity createDataKey="createConfigSecondChildProduct"/> </createData> @@ -118,7 +118,7 @@ <requiredEntity createDataKey="createConfigProductAttr"/> <requiredEntity createDataKey="getConfigAttributeOption"/> </createData> - <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild"> + <createData entity="ConfigurableProductAddChild" stepKey="addConfigurableProductChild"> <requiredEntity createDataKey="createConfigurableProduct"/> <requiredEntity createDataKey="createConfigChildProduct"/> </createData> @@ -133,10 +133,10 @@ <deleteData createDataKey="createSecondSimpleProduct" stepKey="deleteSecondSimpleProduct"/> <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> <deleteData createDataKey="createGroupedProduct" stepKey="deleteGroupedProduct"/> - <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createExportImportConfigurableProduct" stepKey="deleteConfigProduct"/> <deleteData createDataKey="createConfigFirstChildProduct" stepKey="deleteConfigFirstChildProduct"/> <deleteData createDataKey="createConfigSecondChildProduct" stepKey="deleteConfigSecondChildProduct"/> - <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createExportImportConfigurableProductAttribute" stepKey="deleteConfigProductAttribute"/> <deleteData createDataKey="createConfigurableProduct" stepKey="deleteConfigurableProduct"/> <deleteData createDataKey="createConfigChildProduct" stepKey="deleteConfigChildProduct"/> <deleteData createDataKey="createConfigProductAttr" stepKey="deleteConfigProductAttr"/> @@ -144,24 +144,18 @@ <deleteData createDataKey="createExportImportCategory" stepKey="deleteExportImportCategory"/> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> - <waitForPageLoad stepKey="waitForProductIndexPage"/> <actionGroup ref="resetProductGridToDefaultView" stepKey="resetProductGridColumnsInitial"/> <!-- Admin logout--> <actionGroup ref="logout" stepKey="adminLogout"/> </after> - <!-- Go to Catalog > Products and choose one of configurable products: Products page is open --> - <actionGroup ref="filterAndSelectProduct" stepKey="openCreatedConfigurableProduct"> - <argument name="productSku" value="$$createConfigProduct.sku$$"/> - </actionGroup> - <!-- Go to System > Export --> - <actionGroup ref="AdminOpenExportIndexPageActionGroup" stepKey="goToExportPage"/> + <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="goToExportIndexPage"/> <!-- Set Export Settings: Entity Type > Products, SKU > ConfProd's sku and press "Continue" --> <actionGroup ref="exportProductsFilterByAttribute" stepKey="exportProductBySku"> <argument name="attribute" value="sku"/> - <argument name="attributeData" value="$$createConfigProduct.sku$$"/> + <argument name="attributeData" value="$$createExportImportConfigurableProduct.sku$$"/> </actionGroup> <!-- Run cron twice --> @@ -175,7 +169,7 @@ <!-- Go to Catalog > Products. Find ConfProd and delete it --> <actionGroup ref="deleteProductBySku" stepKey="deleteConfigurableProductBySku"> - <argument name="sku" value="$$createConfigProduct.sku$$"/> + <argument name="sku" value="$$createExportImportConfigurableProduct.sku$$"/> </actionGroup> <!-- Go to System > Import. Set import settings: Entity Type > Product, Import Behavior > Add/Update, @@ -188,7 +182,7 @@ <!-- Go to Catalog > Products: Configurable product exists --> <actionGroup ref="filterAndSelectProduct" stepKey="openConfigurableProduct"> - <argument name="productSku" value="$$createConfigProduct.sku$$"/> + <argument name="productSku" value="$$createExportImportConfigurableProduct.sku$$"/> </actionGroup> <!-- Go to "Configurations" section: configurations exist and have images --> @@ -199,8 +193,8 @@ <see selector="{{AdminProductFormConfigurationsSection.currentVariationsSkuCells}}" userInput="$$createConfigSecondChildProduct.sku$$" stepKey="seeSecondProductSkuInField"/> <see selector="{{AdminProductFormConfigurationsSection.currentVariationsPriceCells}}" userInput="$$createConfigFirstChildProduct.price$$" stepKey="seeFirstProductPriceInField"/> <see selector="{{AdminProductFormConfigurationsSection.currentVariationsPriceCells}}" userInput="$$createConfigSecondChildProduct.price$$" stepKey="seeSecondProductPriceInField"/> - <seeElement selector="{{AdminProductFormConfigurationsSection.imageSource(MagentoLogo.fileName)}}" stepKey="seeFirstProductImageInField"/> - <seeElement selector="{{AdminProductFormConfigurationsSection.imageSource(TestImage.fileName)}}" stepKey="seeSecondProductImageInField"/> + <seeElement selector="{{AdminProductFormConfigurationsSection.variationImageSource(MagentoLogo.fileName)}}" stepKey="seeFirstProductImageInField"/> + <seeElement selector="{{AdminProductFormConfigurationsSection.variationImageSource(TestImage.fileName)}}" stepKey="seeSecondProductImageInField"/> <!-- Go to "Images and Videos" section: assert image --> <scrollTo selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" stepKey="scrollToProductGalleryTab"/> @@ -209,7 +203,7 @@ </actionGroup> <!-- Go to any ConfProd's configuration page: Product page open successfully --> - <click selector="{{AdminProductFormConfigurationsSection.productLinkByName($$createConfigFirstChildProduct.name$$)}}" stepKey="clickOnFirstProductLink"/> + <click selector="{{AdminProductFormConfigurationsSection.variationProductLinkByName($$createConfigFirstChildProduct.name$$)}}" stepKey="clickOnFirstProductLink"/> <switchToNextTab stepKey="switchToConfigChildProductPage"/> <waitForPageLoad stepKey="waitForProductPageLoad"/> <!-- Go to "Images and Videos" section: assert image --> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index 58101e8173e52..64b145d44e0e6 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -39,8 +39,8 @@ <element name="variationLabel" type="text" selector="//div[@data-index='configurable-matrix']/label"/> <element name="stepsWizardTitle" type="text" selector="div.content:not([style='display: none;']) .steps-wizard-title"/> <element name="attributeEntityByName" type="text" selector="//div[@class='attribute-entity']//div[normalize-space(.)='{{attributeLabel}}']" parameterized="true"/> - <element name="imageSource" type="text" selector=".admin__control-fields[data-index='thumbnail_image_container'] img[src*='{{imageName}}']" parameterized="true"/> - <element name="productLinkByName" type="text" selector="//a[contains(text(), '{{productName}}')]" parameterized="true"/> + <element name="variationImageSource" type="text" selector="[data-index='configurable-matrix'] [data-index='thumbnail_image_container'] img[src*='{{imageName}}']" parameterized="true"/> + <element name="variationProductLinkByName" type="text" selector="//div[@data-index='configurable-matrix']//*[@data-index='name_container']//a[contains(text(), '{{productName}}')]" parameterized="true"/> </section> <section name="AdminConfigurableProductFormSection"> <element name="productWeight" type="input" selector=".admin__control-text[name='product[weight]']"/> diff --git a/dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv b/dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv deleted file mode 100644 index fc27af79fc162..0000000000000 --- a/dev/tests/acceptance/tests/_data/export_import_configurable_product_2.csv +++ /dev/null @@ -1,2 +0,0 @@ -sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,associated_skus,configurable_variations,configurable_variation_labels -api-configurable-export-import-product,,Default,configurable,Default Category/CategoryExportImport,base,API Configurable Export Import Product,,,2,1,Taxable Goods,"Catalog, Search",123,,,,api-configurable-export-import-product,,,,/m/a/magento-logo.png,Magento Logo,/m/a/magento-logo.png,Magento Logo,/m/a/magento-logo.png,Magento Logo,,,"7/26/19, 8:21 AM","7/26/19, 8:21 AM",,,Block after Info Column,,,,,,,,,,,Use config,,,0,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,0,,,,,,,,,,,,,,,,,,"sku=api-simple-one-export-import,attribute=option1|sku=api-simple-two-export-import,attribute=option2",attribute=attributeExportImport From 2625477ab87e7523e895fecf47d63c5b0491e27c Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Fri, 13 Sep 2019 16:46:53 +0300 Subject: [PATCH 787/841] MC-11557: Check importing of configurable products with images present in filesystem --- .../Test/Mftf/Data/ConfigurableProductData.xml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml index 318791ae9453c..3f21c98068d8a 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml @@ -79,18 +79,9 @@ <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> <!-- Configurable product from file "export_import_configurable_product.csv"--> - <entity name="ApiConfigurableExportImportProduct" type="product"> + <entity name="ApiConfigurableExportImportProduct" extends="ApiConfigurableProduct" type="product"> <data key="sku">api-configurable-export-import-product</data> - <data key="type_id">configurable</data> - <data key="attribute_set_id">4</data> - <data key="visibility">4</data> <data key="name">API Configurable Export Import Product</data> <data key="urlKey">api-configurable-export-import-product</data> - <data key="price">123.00</data> - <data key="weight">2</data> - <data key="status">1</data> - <data key="quantity">100</data> - <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> - <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> </entities> From d807552e8fd96bacb41e8a56d70ea07b01bcd4c9 Mon Sep 17 00:00:00 2001 From: Stas Kozar <stas.kozar@transoftgroup.com> Date: Fri, 13 Sep 2019 16:55:14 +0300 Subject: [PATCH 788/841] MC-11329: Storefront Navigation Menu UI, desktop --- .../Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml | 7 ++++--- .../ActionGroup/AdminChangeMagentoThemeActionGroup.xml | 7 ++++--- .../Theme/Test/Mftf/Section/AdminDesignConfigSection.xml | 1 + .../Test/Mftf/Section/StorefrontNavigationMenuSection.xml | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml index 4df0e151aeab4..ae54b72a5a702 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCatalogNavigationMenuUIDesktopTest.xml @@ -16,6 +16,7 @@ <testCaseId value="MC-11329"/> <severity value="CRITICAL"/> <group value="catalog"/> + <group value="theme"/> </annotations> <before> <!-- Login as admin --> @@ -24,7 +25,7 @@ </before> <after> <actionGroup ref="DeleteDefaultCategoryChildren" stepKey="deleteRootCategoryChildren"/> - <actionGroup ref="AdminChangeMagentoThemeActionGroup" stepKey="changeThemeToDefault"> + <actionGroup ref="AdminChangeStorefrontThemeActionGroup" stepKey="changeThemeToDefault"> <argument name="theme" value="{{MagentoLumaTheme.name}}"/> </actionGroup> <!-- Admin log out --> @@ -32,7 +33,7 @@ </after> <!-- Go to Content > Themes. Change theme to Blank --> - <actionGroup ref="AdminChangeMagentoThemeActionGroup" stepKey="changeThemeToBlank"> + <actionGroup ref="AdminChangeStorefrontThemeActionGroup" stepKey="changeThemeToBlank"> <argument name="theme" value="{{MagentoBlankTheme.name}}"/> </actionGroup> @@ -194,7 +195,7 @@ <deleteData createDataKey="createCategoryWithoutChildrenBlank" stepKey="deleteCategoryWithoutChildrenBlank"/> <!-- Go to Content > Themes. Change theme to Luma --> - <actionGroup ref="AdminChangeMagentoThemeActionGroup" stepKey="changeThemeToLuma"> + <actionGroup ref="AdminChangeStorefrontThemeActionGroup" stepKey="changeThemeToLuma"> <argument name="theme" value="{{MagentoLumaTheme.name}}"/> </actionGroup> diff --git a/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml b/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml index 0b093a1e0e8d8..0620b9b73ba96 100644 --- a/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml +++ b/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml @@ -8,13 +8,14 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminChangeMagentoThemeActionGroup"> + <actionGroup name="AdminChangeStorefrontThemeActionGroup"> <arguments> <argument name="theme" type="string"/> + <argument name="scopeColumn" type="string" defaultValue="Store View"/> + <argument name="scopeName" type="string" defaultValue="{{_defaultStore.name}}"/> </arguments> <amOnPage url="{{DesignConfigPage.url}}" stepKey="navigateToDesignConfigPage"/> - <click selector="{{AdminDesignConfigSection.scopeRow('3')}}" stepKey="editStoreView"/> - <waitForPageLoad stepKey="waitForOpen"/> + <click selector="{{AdminDesignConfigSection.scopeEditLinkByName(scopeColumn, scopeName)}}" stepKey="editScopeConfig"/> <selectOption selector="{{AdminDesignConfigSection.appliedTheme}}" userInput="{{theme}}" stepKey="selectTheme"/> <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSave"/> <waitForElementVisible selector="{{AdminMessagesSection.success}}" stepKey="waitForSuccessMessage"/> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml index 8004d6d6c344f..069068163ccaf 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml @@ -32,5 +32,6 @@ <element name="checkIfStoresArrowExpand" type="button" selector="//li[@id='ZmF2aWNvbi9zdG9yZXM-' and contains(@class,'jstree-closed')]" /> <element name="storeLink" type="button" selector="#ZmF2aWNvbi9zdG9yZXMvMQ-- > a"/> <element name="appliedTheme" type="select" selector="select[name='theme_theme_id']"/> + <element name="scopeEditLinkByName" type="button" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[normalize-space(.)= '{{scope}}']/preceding-sibling::th)+1][contains(.,'{{scopeName}}')]/..//a[contains(@class, 'action-menu-item')]" timeout="30" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml index fd4b0ce453634..5741b50f877f6 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontNavigationMenuSection.xml @@ -11,7 +11,7 @@ <section name="StorefrontNavigationMenuSection"> <element name="navigationMenu" type="block" selector=".section-items.nav-sections-items li"/> <element name="subItemLevelHover" type="text" selector=".{{level}} .submenu a:hover" parameterized="true"/> - <element name="itemByNameAndLevel" type="text" selector="//a[span[contains(., '{{categoryName}}')]]/following-sibling::ul[contains(@class,'{{categoryLevel}}')]" parameterized="true"/> + <element name="itemByNameAndLevel" type="text" selector="//a[span[contains(., '{{itemName}}')]]/following-sibling::ul[contains(@class,'{{itemLevel}}')]" parameterized="true"/> <element name="subItemByLevel" type="text" selector="li.{{itemLevel}}.parent ul.{{itemLevel}}" parameterized="true"/> <element name="itemActiveState" type="text" selector=".navigation .level0.active>.level-top"/> <element name="subItemActiveState" type="text" selector=".navigation .level0 .submenu .active>a"/> From 36e0e27341125fc6a9832a8e4be6e3ae9c28df43 Mon Sep 17 00:00:00 2001 From: Stas Kozar <stas.kozar@transoftgroup.com> Date: Fri, 13 Sep 2019 17:14:43 +0300 Subject: [PATCH 789/841] MC-11329: Storefront Navigation Menu UI, desktop --- ...eActionGroup.xml => AdminChangeStorefrontThemeActionGroup.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/code/Magento/Theme/Test/Mftf/ActionGroup/{AdminChangeMagentoThemeActionGroup.xml => AdminChangeStorefrontThemeActionGroup.xml} (100%) diff --git a/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml b/app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeStorefrontThemeActionGroup.xml similarity index 100% rename from app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeMagentoThemeActionGroup.xml rename to app/code/Magento/Theme/Test/Mftf/ActionGroup/AdminChangeStorefrontThemeActionGroup.xml From 815b0a35f22271388e9bd75a32b57282a9008bc3 Mon Sep 17 00:00:00 2001 From: Daniel Renaud <drenaud@magento.com> Date: Fri, 13 Sep 2019 11:30:13 -0500 Subject: [PATCH 790/841] MC-18514: API functional test to cover filterable custom attributes in layered navigation - fix relevance sorting fixtures --- .../testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php | 2 +- .../Magento/Catalog/_files/products_for_relevance_sorting.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php index 859e83e2a94e3..91f1795935f6a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductSearchTest.php @@ -1486,7 +1486,7 @@ public function testSearchAndSortByRelevance() $this->assertEquals(3, $response['products']['total_count']); $this->assertNotEmpty($response['products']['filters'], 'Filters should have the Category layer'); $this->assertEquals('Colorful Category', $response['products']['filters'][0]['filter_items'][0]['label']); - $productsInResponse = ['Blue briefs','Navy Striped Shoes','Grey shorts']; + $productsInResponse = ['Blue briefs','Navy Blue Striped Shoes','Grey shorts']; $count = count($response['products']['items']); for ($i = 0; $i < $count; $i++) { $this->assertEquals($productsInResponse[$i], $response['products']['items'][$i]['name']); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php index 87a80dfba168c..85b3146fc7ec0 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_for_relevance_sorting.php @@ -61,7 +61,7 @@ ->setAttributeSetId($defaultAttributeSet) ->setStoreId(1) ->setWebsiteIds([1]) - ->setName('Navy Striped Shoes') + ->setName('Navy Blue Striped Shoes') ->setSku('navy-striped-shoes') ->setPrice(40) ->setWeight(8) From d3498b82ed3f6362508396cd81936f2ee67240d3 Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko <ymiroshnychenko@magento.com> Date: Fri, 13 Sep 2019 16:14:09 -0500 Subject: [PATCH 791/841] MC-19421: Reduce q-ty of Reports Created in /app/*/var/report --- .../Magento/Framework/Error/ProcessorTest.php | 8 +- .../Framework/App/ExceptionHandler.php | 12 +-- lib/internal/Magento/Framework/App/Http.php | 5 +- .../App/Test/Unit/ExceptionHandlerTest.php | 6 +- .../Framework/App/Test/Unit/HttpTest.php | 85 ++++++++++++------- pub/errors/processor.php | 2 +- 6 files changed, 68 insertions(+), 50 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php b/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php index 51441e4879539..0ed7bd4440be7 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Error/ProcessorTest.php @@ -19,7 +19,7 @@ class ProcessorTest extends \PHPUnit\Framework\TestCase /** * @inheritdoc */ - public function setUp() + protected function setUp() { $this->processor = $this->createProcessor(); } @@ -28,7 +28,7 @@ public function setUp() * {@inheritdoc} * @throws \Exception */ - public function tearDown() + protected function tearDown() { $reportDir = $this->processor->_reportDir; $this->removeDirRecursively($reportDir); @@ -45,7 +45,7 @@ public function testSaveAndLoadReport( int $logReportDirNestingLevelChanged, string $exceptionMessage ) { - $_ENV['MAGE_LOG_REPORT_DIR_NESTING_LEVEL'] = $logReportDirNestingLevel; + $_ENV['MAGE_ERROR_REPORT_DIR_NESTING_LEVEL'] = $logReportDirNestingLevel; $reportData = [ 0 => $exceptionMessage, 1 => 'exceptionTrace', @@ -60,7 +60,7 @@ public function testSaveAndLoadReport( $this->fail("Failed to generate report id"); } $this->assertEquals($expectedReportData, $processor->reportData); - $_ENV['MAGE_LOG_REPORT_DIR_NESTING_LEVEL'] = $logReportDirNestingLevelChanged; + $_ENV['MAGE_ERROR_REPORT_DIR_NESTING_LEVEL'] = $logReportDirNestingLevelChanged; $processor = $this->createProcessor(); $processor->loadReport($reportId); $this->assertEquals($expectedReportData, $processor->reportData, "File contents of report don't match"); diff --git a/lib/internal/Magento/Framework/App/ExceptionHandler.php b/lib/internal/Magento/Framework/App/ExceptionHandler.php index 38e85326ef5e4..2bec055808aca 100644 --- a/lib/internal/Magento/Framework/App/ExceptionHandler.php +++ b/lib/internal/Magento/Framework/App/ExceptionHandler.php @@ -25,7 +25,7 @@ class ExceptionHandler implements ExceptionHandlerInterface /** * @var Filesystem */ - protected $_filesystem; + private $filesystem; /** * @var LoggerInterface @@ -48,7 +48,7 @@ public function __construct( LoggerInterface $logger ) { $this->encryptor = $encryptor; - $this->_filesystem = $filesystem; + $this->filesystem = $filesystem; $this->logger = $logger; } @@ -166,7 +166,7 @@ private function handleBootstrapErrors( $bootstrapCode = $bootstrap->getErrorCode(); if (Bootstrap::ERR_MAINTENANCE == $bootstrapCode) { // phpcs:ignore Magento2.Security.IncludeFile - require $this->_filesystem + require $this->filesystem ->getDirectoryRead(DirectoryList::PUB) ->getAbsolutePath('errors/503.php'); return true; @@ -214,7 +214,7 @@ private function handleInitException(\Exception $exception): bool if ($exception instanceof InitException) { $this->logger->critical($exception); // phpcs:ignore Magento2.Security.IncludeFile - require $this->_filesystem + require $this->filesystem ->getDirectoryRead(DirectoryList::PUB) ->getAbsolutePath('errors/404.php'); return true; @@ -250,7 +250,7 @@ private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception $reportData['report_id'] = $this->encryptor->getHash(implode('', $reportData)); $this->logger->critical($exception, ['report_id' => $reportData['report_id']]); // phpcs:ignore Magento2.Security.IncludeFile - require $this->_filesystem + require $this->filesystem ->getDirectoryRead(DirectoryList::PUB) ->getAbsolutePath('errors/report.php'); return true; @@ -268,7 +268,7 @@ private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception private function redirectToSetup(Bootstrap $bootstrap, \Exception $exception, ResponseHttp $response) { $setupInfo = new SetupInfo($bootstrap->getParams()); - $projectRoot = $this->_filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath(); + $projectRoot = $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath(); if ($setupInfo->isAvailable()) { $response->setRedirect($setupInfo->getUrl()); $response->sendHeaders(); diff --git a/lib/internal/Magento/Framework/App/Http.php b/lib/internal/Magento/Framework/App/Http.php index e69a858ee5a55..d54dda9e9f166 100644 --- a/lib/internal/Magento/Framework/App/Http.php +++ b/lib/internal/Magento/Framework/App/Http.php @@ -9,7 +9,6 @@ use Magento\Framework\App\Response\Http as ResponseHttp; use Magento\Framework\App\Response\HttpInterface; use Magento\Framework\Controller\ResultInterface; -use Magento\Framework\Event; use Magento\Framework\ObjectManager\ConfigLoaderInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\ObjectManagerInterface; @@ -70,7 +69,7 @@ class Http implements \Magento\Framework\AppInterface /** * @param ObjectManagerInterface $objectManager - * @param Event\Manager $eventManager + * @param Manager $eventManager * @param AreaList $areaList * @param RequestHttp $request * @param ResponseHttp $response @@ -81,7 +80,7 @@ class Http implements \Magento\Framework\AppInterface */ public function __construct( ObjectManagerInterface $objectManager, - Event\Manager $eventManager, + Manager $eventManager, AreaList $areaList, RequestHttp $request, ResponseHttp $response, diff --git a/lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php b/lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php index 7ade77ee40df5..bce2fd8113149 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/ExceptionHandlerTest.php @@ -49,17 +49,17 @@ class ExceptionHandlerTest extends \PHPUnit\Framework\TestCase /** * @var LoggerInterface|MockObject */ - protected $loggerMock; + private $loggerMock; /** * @var ResponseHttp|MockObject */ - protected $responseMock; + private $responseMock; /** * @var RequestHttp|MockObject */ - protected $requestMock; + private $requestMock; protected function setUp() { diff --git a/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php b/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php index 6b5f11b21b9be..5d8e5960e5798 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/HttpTest.php @@ -5,81 +5,100 @@ */ namespace Magento\Framework\App\Test\Unit; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as HelperObjectManager; +use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Framework\App\Request\Http as RequestHttp; +use Magento\Framework\App\Response\Http as ResponseHttp; +use Magento\Framework\App\Http as AppHttp; +use Magento\Framework\App\FrontControllerInterface; +use Magento\Framework\Event\Manager; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\App\AreaList; +use Magento\Framework\App\ObjectManager\ConfigLoader; +use Magento\Framework\App\ExceptionHandlerInterface; +use Magento\Framework\Stdlib\Cookie\CookieReaderInterface; +use Magento\Framework\App\Route\ConfigInterface\Proxy; +use Magento\Framework\App\Request\PathInfoProcessorInterface; +use Magento\Framework\Stdlib\StringUtils; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class HttpTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @var HelperObjectManager */ - protected $objectManager; + private $objectManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ResponseHttp|MockObject */ - protected $responseMock; + private $responseMock; /** - * @var \Magento\Framework\App\Http + * @var AppHttp */ - protected $http; + private $http; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var FrontControllerInterface|MockObject */ - protected $frontControllerMock; + private $frontControllerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var Manager|MockObject */ - protected $eventManagerMock; + private $eventManagerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var RequestHttp|MockObject */ - protected $requestMock; + private $requestMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ObjectManagerInterface|MockObject */ - protected $objectManagerMock; + private $objectManagerMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var AreaList|MockObject */ - protected $areaListMock; + private $areaListMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ConfigLoader|MockObject */ - protected $configLoaderMock; + private $configLoaderMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ExceptionHandlerInterface|MockObject */ - protected $exceptionHandlerMock; + private $exceptionHandlerMock; + /** + * @inheritdoc + */ protected function setUp() { - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $cookieReaderMock = $this->getMockBuilder(\Magento\Framework\Stdlib\Cookie\CookieReaderInterface::class) + $this->objectManager = new HelperObjectManager($this); + $cookieReaderMock = $this->getMockBuilder(CookieReaderInterface::class) ->disableOriginalConstructor() ->getMock(); - $routeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Route\ConfigInterface\Proxy::class) + $routeConfigMock = $this->getMockBuilder(Proxy::class) ->disableOriginalConstructor() ->getMock(); - $pathInfoProcessorMock = $this->getMockBuilder(\Magento\Framework\App\Request\PathInfoProcessorInterface::class) + $pathInfoProcessorMock = $this->getMockBuilder(PathInfoProcessorInterface::class) ->disableOriginalConstructor() ->getMock(); - $converterMock = $this->getMockBuilder(\Magento\Framework\Stdlib\StringUtils::class) + $converterMock = $this->getMockBuilder(StringUtils::class) ->disableOriginalConstructor() ->setMethods(['cleanString']) ->getMock(); - $objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + $objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) + $this->requestMock = $this->getMockBuilder(RequestHttp::class) ->setConstructorArgs( [ 'cookieReader' => $cookieReaderMock, @@ -91,7 +110,7 @@ protected function setUp() ) ->setMethods(['getFrontName', 'isHead']) ->getMock(); - $this->areaListMock = $this->getMockBuilder(\Magento\Framework\App\AreaList::class) + $this->areaListMock = $this->getMockBuilder(AreaList::class) ->disableOriginalConstructor() ->setMethods(['getCodeByFrontName']) ->getMock(); @@ -99,20 +118,20 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['load']) ->getMock(); - $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\Response\Http::class); + $this->objectManagerMock = $this->createMock(ObjectManagerInterface::class); + $this->responseMock = $this->createMock(ResponseHttp::class); $this->frontControllerMock = $this->getMockBuilder(\Magento\Framework\App\FrontControllerInterface::class) ->disableOriginalConstructor() ->setMethods(['dispatch']) ->getMock(); - $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\Manager::class) + $this->eventManagerMock = $this->getMockBuilder(Manager::class) ->disableOriginalConstructor() ->setMethods(['dispatch']) ->getMock(); - $this->exceptionHandlerMock = $this->createMock(\Magento\Framework\App\ExceptionHandlerInterface::class); + $this->exceptionHandlerMock = $this->createMock(ExceptionHandlerInterface::class); $this->http = $this->objectManager->getObject( - \Magento\Framework\App\Http::class, + AppHttp::class, [ 'objectManager' => $this->objectManagerMock, 'eventManager' => $this->eventManagerMock, diff --git a/pub/errors/processor.php b/pub/errors/processor.php index 889eed2cad10e..7cab4add51a92 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -612,7 +612,7 @@ private function getReportPath(int $reportDirNestingLevel, string $reportId): st */ private function getReportDirNestingLevel(string $reportId): int { - $envName = 'MAGE_LOG_REPORT_DIR_NESTING_LEVEL'; + $envName = 'MAGE_ERROR_REPORT_DIR_NESTING_LEVEL'; $value = $_ENV[$envName] ?? getenv($envName); if(false === $value && property_exists($this->_config, 'dir_nesting_level')) { $value = $this->_config->dir_nesting_level; From ad82991a825b5c33682451b1e860cfa84ea9529e Mon Sep 17 00:00:00 2001 From: Yevhen Miroshnychenko <ymiroshnychenko@magento.com> Date: Fri, 13 Sep 2019 16:22:34 -0500 Subject: [PATCH 792/841] MC-19421: Reduce q-ty of Reports Created in /app/*/var/report --- pub/errors/local.xml.sample | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pub/errors/local.xml.sample b/pub/errors/local.xml.sample index bdd980d4f8a2c..c5c35559bd6d0 100644 --- a/pub/errors/local.xml.sample +++ b/pub/errors/local.xml.sample @@ -40,7 +40,7 @@ ... dir_nesting_level=32 -> <magento_root>/var/report/44/ff/b1/08/7a/44/e6/1b/01/8b/3c/de/e7/22/84/d0/17/f2/2e/52/75/5c/24/e5/c8/5c/ba/c1/64/7a/e7/a7/44ffb1087a44e61b018b3cdee72284d017f22e52755c24e5c85cbac1647ae7a7 - If you use an environment variable MAGE_LOG_REPORT_DIR_NESTING_LEVEL, this property will be ignored. + If you use an environment variable MAGE_ERROR_REPORT_DIR_NESTING_LEVEL, this property will be ignored. Environment variable has a higher priority. --> <dir_nesting_level>0</dir_nesting_level> From c090d38812525538585b109c52b31c1c6f733a7d Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Fri, 13 Sep 2019 17:18:25 -0500 Subject: [PATCH 793/841] MC-18996: GraphQl Url Resolver doesn't return any results if the url_key doesn't have the extension - add url prefix to schema --- .../Model/Resolver/CategoryUrlSuffix.php | 77 +++++++++++++++++++ .../Model/Resolver/ProductUrlSuffix.php | 77 +++++++++++++++++++ .../etc/schema.graphqls | 5 ++ 3 files changed, 159 insertions(+) create mode 100644 app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php create mode 100644 app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php new file mode 100644 index 0000000000000..a68ab0916e42a --- /dev/null +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewriteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Returns the url suffix for catalog + */ +class CategoryUrlSuffix implements ResolverInterface +{ + const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/category_url_suffix'; + + /** + * Cache for product rewrite suffix + * + * @var array + */ + private $productUrlSuffix = []; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param array $productUrlSuffix + */ + public function __construct(ScopeConfigInterface $scopeConfig, array $productUrlSuffix = []) + { + $this->productUrlSuffix = $productUrlSuffix; + $this->scopeConfig = $scopeConfig; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ): string { + $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + return $this->getProductUrlSuffix($storeId); + } + + /** + * Retrieve product url suffix by store + * + * @param int $storeId + * @return string + */ + private function getProductUrlSuffix(int $storeId): string + { + if (!isset($this->productUrlSuffix[$storeId])) { + $this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue( + self::XML_PATH_PRODUCT_URL_SUFFIX, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + return $this->productUrlSuffix[$storeId]; + } +} diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php new file mode 100644 index 0000000000000..65d2b33f70b45 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewriteGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Returns the url suffix for product + */ +class ProductUrlSuffix implements ResolverInterface +{ + const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/product_url_suffix'; + + /** + * Cache for product rewrite suffix + * + * @var array + */ + private $productUrlSuffix = []; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param array $productUrlSuffix + */ + public function __construct(ScopeConfigInterface $scopeConfig, array $productUrlSuffix = []) + { + $this->productUrlSuffix = $productUrlSuffix; + $this->scopeConfig = $scopeConfig; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ): string { + $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + return $this->getProductUrlSuffix($storeId); + } + + /** + * Retrieve product url suffix by store + * + * @param int $storeId + * @return string + */ + private function getProductUrlSuffix(int $storeId): string + { + if (!isset($this->productUrlSuffix[$storeId])) { + $this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue( + self::XML_PATH_PRODUCT_URL_SUFFIX, + ScopeInterface::SCOPE_STORE, + $storeId + ); + } + return $this->productUrlSuffix[$storeId]; + } +} diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls index 89108e578d673..a5b9ee48bd208 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls @@ -3,10 +3,15 @@ interface ProductInterface { url_key: String @doc(description: "The part of the URL that identifies the product") + url_suffix: String @doc(description: "The part of the URL that is appended after the url key") @resolver(class: "Magento\\CatalogUrlRewriteGraphQl\\Model\\Resolver\\ProductUrlSuffix") url_path: String @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") url_rewrites: [UrlRewrite] @doc(description: "URL rewrites list") @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite") } +interface CategoryInterface { + url_suffix: String @doc(description: "The part of the URL that is appended after the url key") @resolver(class: "Magento\\CatalogUrlRewriteGraphQl\\Model\\Resolver\\CategoryUrlSuffix") +} + input ProductFilterInput { url_key: FilterTypeInput @doc(description: "The part of the URL that identifies the product") url_path: FilterTypeInput @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") From b0e70c798b4118609f52b4abfa8684d9ddd46f48 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sat, 14 Sep 2019 07:00:35 +0000 Subject: [PATCH 794/841] MC-19862: [Function Test] MC-148 Magento\FunctionalTestingFramework.functional.ApplyCatalogPriceRuleByProductAttributeTest --- .../Controller/Adminhtml/Promo/Catalog/Save.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php index 4f58293d53359..996fc4e8ef3d2 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php @@ -12,6 +12,7 @@ use Magento\Framework\Registry; use Magento\Framework\Stdlib\DateTime\Filter\Date; use Magento\Framework\App\Request\DataPersistorInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; /** * Save action for catalog rule @@ -25,19 +26,27 @@ class Save extends \Magento\CatalogRule\Controller\Adminhtml\Promo\Catalog imple */ protected $dataPersistor; + /** + * @var TimezoneInterface + */ + private $localeDate; + /** * @param Context $context * @param Registry $coreRegistry * @param Date $dateFilter * @param DataPersistorInterface $dataPersistor + * @param TimezoneInterface $localeDate */ public function __construct( Context $context, Registry $coreRegistry, Date $dateFilter, - DataPersistorInterface $dataPersistor + DataPersistorInterface $dataPersistor, + TimezoneInterface $localeDate ) { $this->dataPersistor = $dataPersistor; + $this->localeDate = $localeDate; parent::__construct($context, $coreRegistry, $dateFilter); } @@ -66,6 +75,9 @@ public function execute() ); $data = $this->getRequest()->getPostValue(); + if (!$this->getRequest()->getParam('from_date')) { + $data['from_date'] = $this->localeDate->formatDate(); + } $filterValues = ['from_date' => $this->_dateFilter]; if ($this->getRequest()->getParam('to_date')) { $filterValues['to_date'] = $this->_dateFilter; From 6f11d16b9d4444e9ebab83ad56c0876d12e2069f Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@magento.com> Date: Sat, 14 Sep 2019 10:44:56 -0300 Subject: [PATCH 795/841] magento-engcom/magento2ce#3248: Code style fixes --- app/code/Magento/Customer/Model/Customer.php | 4 ++-- lib/web/mage/adminhtml/browser.js | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 34f1e5e2da467..1f8f7d90f6d0d 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -513,7 +513,7 @@ public function getAddressCollection() /** * Customer addresses collection * - * @return ResourceModel\Address\Collection + * @return \Magento\Customer\Model\ResourceModel\Address\Collection * @throws \Magento\Framework\Exception\LocalizedException */ public function getAddressesCollection() @@ -600,7 +600,7 @@ public function hashPassword($password, $salt = true) * Validate password with salted hash * * @param string $password - * @return bool + * @return boolean * @throws \Exception */ public function validatePassword($password) diff --git a/lib/web/mage/adminhtml/browser.js b/lib/web/mage/adminhtml/browser.js index 20bcdd36baca5..137ad5541a87f 100644 --- a/lib/web/mage/adminhtml/browser.js +++ b/lib/web/mage/adminhtml/browser.js @@ -52,10 +52,11 @@ define([ content = '<div class="popup-window" id="' + windowId + '"></div>', self = this; - if (this.modalLoaded === true - && options - && self.targetElementId - && self.targetElementId === options.targetElementId) { + if (this.modalLoaded === true && + options && + self.targetElementId && + self.targetElementId === options.targetElementId + ) { if (typeof options.closed !== 'undefined') { this.modal.modal('option', 'closed', options.closed); } From d009e26e9a199dbddc66cb43832eb1f571c25d59 Mon Sep 17 00:00:00 2001 From: Alexandre Thurow <alexandre@trezo.com.br> Date: Sat, 14 Sep 2019 11:38:30 -0300 Subject: [PATCH 796/841] fixing notification counter when get's more than 1 digit until 99+ --- .../source/module/header/actions-group/_notifications.less | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less index 40ebb6f3c4569..03fe148ff9c6c 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less @@ -97,12 +97,13 @@ display: inline-block; font-size: @notifications__font-size; font-weight: @font-weight__bold; - height: 18px; + height: 20px; left: 50%; margin-left: .3em; margin-top: -1.1em; - min-width: 18px; - padding: .3em .5em; + min-width: 20px; + line-height: 20px; + text-align: center; position: absolute; top: 50%; } From 3187c6167bff7cc925249de829ebbeb2ac02dc13 Mon Sep 17 00:00:00 2001 From: Alexandre Thurow <alexandre.fredericothurow@gmail.com> Date: Sat, 14 Sep 2019 14:35:37 -0300 Subject: [PATCH 797/841] fixing alphabetical problem --- .../css/source/module/header/actions-group/_notifications.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less index 03fe148ff9c6c..b63783618c473 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less @@ -99,10 +99,10 @@ font-weight: @font-weight__bold; height: 20px; left: 50%; + line-height: 20px; margin-left: .3em; margin-top: -1.1em; min-width: 20px; - line-height: 20px; text-align: center; position: absolute; top: 50%; From 07d6cb915944a6a7d43f82177f151c233fa856fc Mon Sep 17 00:00:00 2001 From: Alexandre Thurow <alexandre.fredericothurow@gmail.com> Date: Sat, 14 Sep 2019 14:39:14 -0300 Subject: [PATCH 798/841] fixing alphabetical problem --- .../css/source/module/header/actions-group/_notifications.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less index b63783618c473..6b30bf70772a4 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_notifications.less @@ -103,8 +103,8 @@ margin-left: .3em; margin-top: -1.1em; min-width: 20px; - text-align: center; position: absolute; + text-align: center; top: 50%; } } From 5bb1a40efe2509234d21370c8b91c4899fa74246 Mon Sep 17 00:00:00 2001 From: Dmytro Horytskyi <horytsky@adobe.com> Date: Sat, 14 Sep 2019 22:12:35 +0000 Subject: [PATCH 799/841] MC-19862: [Function Test] MC-148 Magento\FunctionalTestingFramework.functional.ApplyCatalogPriceRuleByProductAttributeTest --- .../CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php | 4 +--- .../Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php index 996fc4e8ef3d2..6d499b93e411f 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Catalog/Save.php @@ -55,16 +55,15 @@ public function __construct( * * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface|void * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function execute() { if ($this->getRequest()->getPostValue()) { - /** @var \Magento\CatalogRule\Api\CatalogRuleRepositoryInterface $ruleRepository */ $ruleRepository = $this->_objectManager->get( \Magento\CatalogRule\Api\CatalogRuleRepositoryInterface::class ); - /** @var \Magento\CatalogRule\Model\Rule $model */ $model = $this->_objectManager->create(\Magento\CatalogRule\Model\Rule::class); @@ -74,7 +73,6 @@ public function execute() ['request' => $this->getRequest()] ); $data = $this->getRequest()->getPostValue(); - if (!$this->getRequest()->getParam('from_date')) { $data['from_date'] = $this->localeDate->formatDate(); } diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php index e589c8595ce2c..944710773123f 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php @@ -101,7 +101,9 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false) $scopeTz = new \DateTimeZone( $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId) ); - $fromTime = (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp(); + $fromTime = $rule->getFromDate() + ? (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp() + : 0; $toTime = $rule->getToDate() ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1 : 0; From 6ab019b21eb90d9ebe119d8777a111a4b88bfad8 Mon Sep 17 00:00:00 2001 From: Krzysztof Daniel <krzysztof.daniel@creativestyle.pl> Date: Sun, 15 Sep 2019 15:54:05 +0200 Subject: [PATCH 800/841] Fizes problem with wrong image URL in sitemap Problem was caused by wrong context of generation. It was adminhtml and for this area URL for selected image sizes are not defined (as those sizes are not defined). Fixed by using Emulation. --- .../Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php index 8eeeb5bf6bc12..d5e1984bf3204 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php @@ -49,7 +49,9 @@ public function execute() // if sitemap record exists if ($sitemap->getId()) { try { + $this->appEmulation->startEnvironmentEmulation($sitemap->getStoreId(),\Magento\Framework\App\Area::AREA_FRONTEND, true); $sitemap->generateXml(); + $this->appEmulation->stopEnvironmentEmulation(); $this->messageManager->addSuccessMessage( __('The sitemap "%1" has been generated.', $sitemap->getSitemapFilename()) ); From b7059a615b59b673690f0f3d8187f684213fc72d Mon Sep 17 00:00:00 2001 From: Krzysztof Daniel <krzysztof.daniel@creativestyle.pl> Date: Sun, 15 Sep 2019 16:31:16 +0200 Subject: [PATCH 801/841] Fix Code style --- .../Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php index d5e1984bf3204..bf3fa8d95b211 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php @@ -49,7 +49,8 @@ public function execute() // if sitemap record exists if ($sitemap->getId()) { try { - $this->appEmulation->startEnvironmentEmulation($sitemap->getStoreId(),\Magento\Framework\App\Area::AREA_FRONTEND, true); + $this->appEmulation->startEnvironmentEmulation($sitemap->getStoreId(), + \Magento\Framework\App\Area::AREA_FRONTEND, true); $sitemap->generateXml(); $this->appEmulation->stopEnvironmentEmulation(); $this->messageManager->addSuccessMessage( From 861e9f9401dd7845a77e63fb6cddd5359064c027 Mon Sep 17 00:00:00 2001 From: Krzysztof Daniel <krzysztof.daniel@creativestyle.pl> Date: Sun, 15 Sep 2019 17:04:03 +0200 Subject: [PATCH 802/841] Fix Code style --- .../Sitemap/Controller/Adminhtml/Sitemap/Generate.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php index bf3fa8d95b211..5cfc7349888f3 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php @@ -49,8 +49,11 @@ public function execute() // if sitemap record exists if ($sitemap->getId()) { try { - $this->appEmulation->startEnvironmentEmulation($sitemap->getStoreId(), - \Magento\Framework\App\Area::AREA_FRONTEND, true); + $this->appEmulation->startEnvironmentEmulation( + $sitemap->getStoreId(), + \Magento\Framework\App\Area::AREA_FRONTEND, + true + ); $sitemap->generateXml(); $this->appEmulation->stopEnvironmentEmulation(); $this->messageManager->addSuccessMessage( From 0c45902eeeddb660ecdeaeabda2d0a06e514c316 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 15 Sep 2019 22:49:37 +0100 Subject: [PATCH 803/841] remove confusing lines from Magento_Analytics ReadMe --- app/code/Magento/Analytics/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Analytics/README.md b/app/code/Magento/Analytics/README.md index e0f1c818c7a94..7ec30c6dd484b 100644 --- a/app/code/Magento/Analytics/README.md +++ b/app/code/Magento/Analytics/README.md @@ -5,7 +5,6 @@ The Magento_Analytics module integrates your Magento instance with the [Magento The module implements the following functionality: - Enabling subscription to Magento Business Intelligence (MBI) and automatic re-subscription -- Changing the base URL with the same MBI account remained - Declaring the configuration schemas for report data collection - Collecting the Magento instance data as reports for MBI - Introducing API that provides the collected data From 08a77d7625c9cc8c7d674031d938c5c6573d94e9 Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Sun, 15 Sep 2019 23:49:46 +0100 Subject: [PATCH 804/841] Update Magento_Backend module ReadMe --- app/code/Magento/Backend/README.md | 115 ++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Backend/README.md b/app/code/Magento/Backend/README.md index 03c7d86516b92..205051809328a 100644 --- a/app/code/Magento/Backend/README.md +++ b/app/code/Magento/Backend/README.md @@ -1,3 +1,112 @@ -The Backend module contains common infrastructure and assets for other modules to be defined and used in their -administration user interface (UI). It does not contain anything specific to other modules. Among many things it -handles the logic of authenticating and authorizing users. +# Magento_Backend module + +The Magento_Backend module contains common infrastructure and assets for other modules to be defined and used in their +administration user interface (UI). + +The Magento_Backend module does not contain anything specific to other modules. Among many things it handles the logic of authenticating and authorizing users. + +## Installation details + +Before disabling or uninstalling this module, note that the following modules depends on this module: + +- Magento_Analytics +- Magento_Authorization +- Magento_NewRelicReporting +- Magento_ProductVideo +- Magento_ReleaseNotification +- Magento_Search +- Magento_Security +- Magento_Signifyd +- Magento_Swatches +- Magento_Ui +- Magento_User +- Magento_Webapi + +For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html). + +## Structure + +Beyond the [usual module file structure](https://devdocs.magento.com/guides/v2.3/architecture/archi_perspectives/components/modules/mod_intro.html) the module contains a directory `Service/V1`. + +`Service/V1` - contains logic to provide a list of modules installed in Magento. + +For information about typical file structure of a module in Magento 2, see [Module file structure](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/build/module-file-structure.html#module-file-structure). + +## Extensibility + +Extension developers can interact with the Magento_Backend module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_Backend module. + +### Events + +The module dispatches the following events: + + - `adminhtml_block_html_before` event in the `\Magento\Backend\Block\Template::_toHtml()` method. Parameters: + - `block` is the backend block template (this) (`\Magento\Backend\Block\Template` class). + - `adminhtml_store_edit_form_prepare_form` event in the `\Magento\Backend\Block\System\Store\Edit\AbstractForm::_prepareForm()` method. Parameters: + - `block` is the AbstractForm block (this) (`\Magento\Backend\Block\System\Store\Edit\AbstractForm` class). + - `backend_block_widget_grid_prepare_grid_before` event in the `\Magento\Backend\Block\Widget\Grid::_prepareGrid()` method. Parameters: + - `grid` is the widget grid block (this) (`\Magento\Backend\Block\Widget\Grid` class) + - `collection` is the grid collection (`\Magento\Framework\Data\Collection` class). + - `adminhtml_cache_flush_system` event in the `\Magento\Backend\Console\Command\CacheCleanCommand::performAction()` method. + - `adminhtml_cache_flush_all` event in the `\Magento\Backend\Console\Command\CacheFlushCommand::performAction()` method. + - `clean_catalog_images_cache_after` event in the `\Magento\Backend\Controller\Adminhtml\Cache\CleanImages::execute()` method. + - `clean_media_cache_after` event in the `\Magento\Backend\Controller\Adminhtml\Cache\CleanMedia::execute()` method. + - `clean_static_files_cache_after` event in the `\Magento\Backend\Controller\Adminhtml\Cache\CleanStaticFiles::execute()` method. + - `adminhtml_cache_flush_all` event in the `\Magento\Backend\Controller\Adminhtml\Cache\FlushAll::execute()` method. + - `adminhtml_cache_flush_system` event in the `\Magento\Backend\Controller\Adminhtml\Cache\FlushSystem::execute()` method. + - `theme_save_after` event in the `\Magento\Backend\Controller\Adminhtml\System\Design\Save::execute()` method. + - `backend_auth_user_login_success` event in the `\Magento\Backend\Model\Auth::login()` method. Parameters: + - `user` is the credential storage object (`null | \Magento\Backend\Model\Auth\Credential\StorageInterface`) + - `backend_auth_user_login_failed` event in the `\Magento\Backend\Model\Auth::login()` method. Parameters: + - `user_name` is username extracted from the credential storage object (`null | \Magento\Backend\Model\Auth\Credential\StorageInterface`) + - `exception` any exception generated (`\Magento\Framework\Exception\LocalizedException | \Magento\Framework\Exception\Plugin\AuthenticationException`) + +For information about an event in Magento 2, see [Events and observers](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events). + +### Layouts + +This module introduces the following layouts and layout handles in the `view/adminhtml/layout` directory: + +- `admin_login` +- `adminhtml_auth_login` +- `adminhtml_cache_block` +- `adminhtml_cache_index` +- `adminhtml_dashboard_customersmost` +- `adminhtml_dashboard_customersnewest` +- `adminhtml_dashboard_index` +- `adminhtml_dashboard_productsviewed` +- `adminhtml_denied` +- `adminhtml_noroute` +- `adminhtml_system_account_index` +- `adminhtml_system_design_edit` +- `adminhtml_system_design_grid` +- `adminhtml_system_design_grid_block` +- `adminhtml_system_design_index` +- `adminhtml_system_store_deletestore` +- `adminhtml_system_store_editstore` +- `adminhtml_system_store_grid_block` +- `adminhtml_system_store_index` +- `default` +- `editor` +- `empty` +- `formkey` +- `overlay_popup` +- `popup` + + +For more information about layouts in Magento 2, see the [Layout documentation](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/layout-overview.html). + +### UI components + +You can extend Magento_Backend module using the following configuration files: + +- `view/adminhtml/ui_component/design_config_form.xml` +- `view/adminhtml/ui_component/design_config_listing.xml` + +For information about UI components in Magento 2, see [Overview of UI components](https://devdocs.magento.com/guides/v2.3/ui_comp_guide/bk-ui_comps.html). + +## Additional information + +For information about significant changes in patch releases, see [2.3.x Release information](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html). From d17996f7f49dcc16a23554617a7bce87f1bc277b Mon Sep 17 00:00:00 2001 From: Raul E Watson <diazwatson@gmail.com> Date: Mon, 16 Sep 2019 00:05:10 +0100 Subject: [PATCH 805/841] Update Magento_Backup module ReadMe --- app/code/Magento/Backup/README.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backup/README.md b/app/code/Magento/Backup/README.md index 59688ea3e716e..fe5a34d84d0c0 100644 --- a/app/code/Magento/Backup/README.md +++ b/app/code/Magento/Backup/README.md @@ -1,3 +1,28 @@ -The Backup module allows administrators to perform backups and rollbacks. Types of backups include system, database and media backups. This module relies on the Cron module to schedule backups. +# Magento_Backup module -This module does not affect the storefront. +The Magento_Backup allows administrators to perform backups and rollbacks. Types of backups include system, database and media backups. This module relies on the Cron module to schedule backups. + +The Magento_Backup does not affect the storefront. + +For more information about this module, see [Magento Backups](https://docs.magento.com/m2/ce/user_guide/system/backups.html) + +## Extensibility + +Extension developers can interact with the Magento_Backup module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html). + +[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_Backup module. + +### Layouts + +This module introduces the following layouts and layout handles in the `view/adminhtml/layout` directory: + +`backup_index_block` +`backup_index_disabled` +`backup_index_grid` +`backup_index_index` + +For more information about layouts in Magento 2, see the [Layout documentation](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/layout-overview.html). + +## Additional information + +For information about significant changes in patch releases, see [2.3.x Release information](https://devdocs.magento.com/guides/v2.3/release-notes/bk-release-notes.html). From f54e506d7b4ec31cbda52db89d001a7f98640b38 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Mon, 16 Sep 2019 08:15:09 +0300 Subject: [PATCH 806/841] MC-11557: Check importing of configurable products with images present in filesystem --- .../Catalog/Test/Mftf/Data/ProductData.xml | 22 ++----------------- ...mportConfigurableProductWithImagesTest.xml | 2 +- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 25e3e39b20d99..e122615eb8aa4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -1171,31 +1171,13 @@ <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> <!-- Products from file "export_import_configurable_product.csv" --> - <entity name="ApiSimpleOneExportImport" type="product2"> + <entity name="ApiSimpleOneExportImport" extends="ApiSimpleOne" type="product2"> <data key="sku">api-simple-one-export-import</data> - <data key="type_id">simple</data> - <data key="attribute_set_id">4</data> - <data key="visibility">4</data> <data key="name">Api Simple Product One Export Import</data> - <data key="price">123.00</data> - <data key="urlKey" unique="suffix">api-simple-one-export-import</data> - <data key="status">1</data> - <data key="quantity">100</data> - <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> - <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> - <entity name="ApiSimpleTwoExportImport" type="product2"> + <entity name="ApiSimpleTwoExportImport" extends="ApiSimpleTwo" type="product2"> <data key="sku">api-simple-two-export-import</data> - <data key="type_id">simple</data> - <data key="attribute_set_id">4</data> - <data key="visibility">4</data> <data key="name">Api Simple Product Two Export Import</data> - <data key="price">123.00</data> - <data key="urlKey" unique="suffix">api-simple-two-export-import</data> - <data key="status">1</data> - <data key="quantity">100</data> - <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> - <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> <entity name="SimpleProductPrice10Qty1" type="product"> <data key="sku" unique="suffix">simple-product_</data> diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index 0124ff5a16dda..7c6c36b07ee2d 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -16,7 +16,7 @@ <description value="Check importing of configurable products with images present in filesystem"/> <severity value="CRITICAL"/> <testCaseId value="MC-11557"/> - <group value="configurableproduct"/> + <group value="configurable_product"/> </annotations> <before> <!-- Create sample data: From 9d8128eeb4fe55f3fdcaa1d7b16b75064bbdc11d Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Mon, 16 Sep 2019 10:26:45 +0300 Subject: [PATCH 807/841] MC-13104: Cannot upload incorrect file --- .../Catalog/Test/Mftf/Data/ProductData.xml | 5 ++ ...hIncorrectNameThroughCustomOptionsTest.xml | 71 +++++++++++++++++++ ...ithout <script><:script> tags...\")'>.png" | 0 3 files changed, 76 insertions(+) create mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml create mode 100644 "dev/tests/acceptance/tests/_data/<img src=x onerror='alert(\"XSS without <script><:script> tags...\")'>.png" diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 517ab253b8238..9ba1adf31316f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -512,6 +512,11 @@ <requiredEntity type="product_option">ProductOptionArea</requiredEntity> <requiredEntity type="product_option">ProductOptionFile</requiredEntity> </entity> + <entity name="ProductFileOptionWithScriptTag" type="product"> + <var key="sku" entityType="product" entityKey="sku"/> + <data key="file"><img src=x onerror='alert("XSS without <script><:script> tags...")'>.png</data> + <requiredEntity type="product_option">ProductOptionFile</requiredEntity> + </entity> <entity name="ApiVirtualProductWithDescription" type="product"> <data key="sku" unique="suffix">api-virtual-product</data> <data key="type_id">virtual</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml new file mode 100644 index 0000000000000..2ea8e8820b1b4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest"> + <annotations> + <features value="Catalog"/> + <stories value="Custom options"/> + <title value="Verify cannot load file with incorrect name through Custom options"/> + <description value="Verify cannot load file with incorrect name through Custom options"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13104"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Create customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <!-- Create category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <!-- Create simple product --> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Add file upload custom option to the product --> + <updateData createDataKey="createProduct" entity="ProductFileOptionWithScriptTag" stepKey="updateProductWithOption"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + </before> + <after> + <!-- Delete product --> + <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete customer --> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> + </after> + + <!-- Login to storefront --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Open product page --> + <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> + <argument name="productUrlKey" value="$$createProduct.custom_attributes[url_key]$$"/> + </actionGroup> + + <!-- Upload file --> + <actionGroup ref="StorefrontAttachOptionFileActionGroup" stepKey="selectAndAttachFile"> + <argument name="optionTitle" value="ProductOptionFile"/> + <argument name="file" value="ProductFileOptionWithScriptTag.file"/> + </actionGroup> + + <!-- Add product to cart --> + <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCartButton"/> + <waitForPageLoad stepKey="waitForProductAddToCart"/> + + <!-- Assert alert message --> + <waitForElementVisible selector="{{StorefrontProductPageSection.alertMessage}}" stepKey="waitForElementVisible"/> + <see selector="{{StorefrontProductPageSection.alertMessage}}" userInput="The file is empty. Select another file and try again." stepKey="seeAlertMessage"/> + + <!-- Assert cart is empty --> + <actionGroup ref="assertMiniCartEmpty" stepKey="assertMiniCartEmpty"/> + </test> +</tests> diff --git "a/dev/tests/acceptance/tests/_data/<img src=x onerror='alert(\"XSS without <script><:script> tags...\")'>.png" "b/dev/tests/acceptance/tests/_data/<img src=x onerror='alert(\"XSS without <script><:script> tags...\")'>.png" new file mode 100644 index 0000000000000..e69de29bb2d1d From 0c36ccde066b4a5e6d643211a9a2e721e8f9d9b7 Mon Sep 17 00:00:00 2001 From: DianaRusin <rusind95@gmail.com> Date: Mon, 16 Sep 2019 10:50:42 +0300 Subject: [PATCH 808/841] MC-11386: Verify Category Product and Product Category partial reindex --- app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml | 2 +- app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml index 05a6bae9ab0fc..b451096f2c3a8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -118,6 +118,6 @@ <data key="include_in_menu">true</data> </entity> <entity name="SubCategoryNonAnchor" extends="SubCategoryWithParent"> - <requiredEntity type="custom_attribute">CustomAttributeCategoryIsAnchor</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeCategoryNonAnchor</requiredEntity> </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml index 69d8c3c87ae5b..7bd392f0aa74a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml @@ -51,7 +51,7 @@ <data key="attribute_code">short_description</data> <data key="value">Short Fixedtest 555</data> </entity> - <entity name="CustomAttributeCategoryIsAnchor" type="custom_attribute"> + <entity name="CustomAttributeCategoryNonAnchor" type="custom_attribute"> <data key="attribute_code">is_anchor</data> <data key="value">0</data> </entity> From 5b77eb849dd4b94cf49cfd6060d9ebe2856b7f8a Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 16 Sep 2019 13:18:45 +0300 Subject: [PATCH 809/841] MC-20139: Wishlist Items of customers not displaying on admin for secondary store --- .../ResourceModel/Item/Collection/Grid.php | 94 ++++++++++++------- .../ResourceModel/Item/Collection/Grid.php | 76 +++++++++++++++ .../_files/wishlist_on_second_website.php | 27 ++++++ .../wishlist_on_second_website_rollback.php | 9 ++ 4 files changed, 171 insertions(+), 35 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website_rollback.php diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index fb6b647811abb..5d2ab2db3fc77 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -6,15 +6,25 @@ namespace Magento\Wishlist\Model\ResourceModel\Item\Collection; -use Magento\Customer\Controller\RegistryConstants as RegistryConstants; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Magento\Customer\Controller\RegistryConstants; +use Magento\Wishlist\Model\Item; /** - * Wishlist item collection grouped by customer id + * Wishlist item collection for grid grouped by customer id * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Grid extends \Magento\Wishlist\Model\ResourceModel\Item\Collection { + /** + * Load product attributes to present in grid + */ + private const PRODUCT_ATTRIBUTES_TO_GRID = [ + 'name', + 'price', + ]; + /** * @var \Magento\Framework\Registry */ @@ -88,30 +98,48 @@ public function __construct( } /** - * Initialize db select - * - * @return $this + * @inheritdoc */ protected function _initSelect() { parent::_initSelect(); - $this->addCustomerIdFilter( - $this->_registryManager->registry(RegistryConstants::CURRENT_CUSTOMER_ID) - ) - ->resetSortOrder() - ->addDaysInWishlist() + + $customerId = $this->_registryManager->registry(RegistryConstants::CURRENT_CUSTOMER_ID); + $this->addDaysInWishlist() ->addStoreData() - ->setVisibilityFilter() - ->setInStockFilter(); + ->addCustomerIdFilter($customerId) + ->resetSortOrder(); + return $this; } /** - * Add select order - * - * @param string $field - * @param string $direction - * @return \Magento\Framework\Data\Collection\AbstractDb + * @inheritdoc + */ + protected function _assignProducts() + { + /** @var ProductCollection $productCollection */ + $productCollection = $this->_productCollectionFactory->create() + ->addAttributeToSelect(self::PRODUCT_ATTRIBUTES_TO_GRID) + ->addIdFilter($this->_productIds); + + /** @var Item $item */ + foreach ($this as $item) { + $product = $productCollection->getItemById($item->getProductId()); + if ($product) { + $product->setCustomOptions([]); + $item->setProduct($product); + $item->setProductName($product->getName()); + $item->setName($product->getName()); + $item->setPrice($product->getPrice()); + } + } + + return $this; + } + + /** + * @inheritdoc */ public function setOrder($field, $direction = self::SORT_ORDER_DESC) { @@ -127,24 +155,7 @@ public function setOrder($field, $direction = self::SORT_ORDER_DESC) } /** - * Add quantity to filter - * - * @param string $field - * @param array $condition - * @return \Magento\Wishlist\Model\ResourceModel\Item\Collection - */ - private function addQtyFilter(string $field, array $condition) - { - return parent::addFieldToFilter('main_table.' . $field, $condition); - } - - /** - * Add field filter to collection - * - * @param string|array $field - * @param null|string|array $condition - * @see self::_getConditionSql for $condition - * @return \Magento\Framework\Data\Collection\AbstractDb + * @inheritdoc */ public function addFieldToFilter($field, $condition = null) { @@ -168,6 +179,19 @@ public function addFieldToFilter($field, $condition = null) return $this->addQtyFilter($field, $condition); } } + return parent::addFieldToFilter($field, $condition); } + + /** + * Add quantity to filter + * + * @param string $field + * @param array $condition + * @return \Magento\Wishlist\Model\ResourceModel\Item\Collection + */ + private function addQtyFilter(string $field, array $condition) + { + return parent::addFieldToFilter('main_table.' . $field, $condition); + } } diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php new file mode 100644 index 0000000000000..f7d7199134013 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Model\ResourceModel\Item\Collection; + +use Magento\Customer\Controller\RegistryConstants; +use Magento\Customer\Model\Customer; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Registry; +use Magento\Store\Model\Website; +use PHPUnit\Framework\TestCase; + +/** + * Class to test wishlist collection by customer functionality + * + * @magentoAppArea adminhtml + */ +class GridTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var Registry + */ + private $registryManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = ObjectManager::getInstance(); + $this->registryManager = $this->objectManager->get(Registry::class); + } + + /** + * Test to load wishlist collection by customer on second website + * + * @magentoDbIsolation disabled + * @magentoDataFixture Magento/Wishlist/_files/wishlist_on_second_website.php + */ + public function testLoadOnSecondWebsite() + { + $customer = $this->loadCustomer(); + $this->registryManager->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customer->getId()); + + $gridCollection = $this->objectManager->get(Grid::class); + $this->assertNotEmpty($gridCollection->getItems()); + } + + /** + * Load customer in second website + * + * @return Customer + */ + private function loadCustomer(): Customer + { + /** @var $website Website */ + $website = $this->objectManager->get(Website::class); + $website->load('newwebsite', 'code'); + + /** @var Customer $customer */ + $customer = $this->objectManager->get(Customer::class); + $customer->setWebsiteId($website->getId()); + $customer->loadByEmail('customer2@example.com'); + + return $customer; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website.php new file mode 100644 index 0000000000000..6d8051cf060f6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Catalog/_files/products_with_websites_and_stores.php'; +require __DIR__ . '/../../../Magento/Customer/_files/customer_non_default_website_id.php'; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Wishlist\Model\Wishlist; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->get(ProductRepositoryInterface::class); +$simpleProduct = $productRepository->get('simple-2'); + +/* @var $wishlist Wishlist */ +$wishlist = Bootstrap::getObjectManager()->create(Wishlist::class); +$wishlist->loadByCustomerId($customer->getId(), true); +$wishlist->addNewItem($simpleProduct); +$wishlist->setSharingCode('fixture_unique_code') + ->setShared(1) + ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website_rollback.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website_rollback.php new file mode 100644 index 0000000000000..49b3e120f7354 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist_on_second_website_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/../../../Magento/Catalog/_files/products_with_websites_and_stores_rollback.php'; +require __DIR__ . '/../../../Magento/Customer/_files/customer_non_default_website_id_rollback.php'; From 86812b90a750b0b691923208f89d2cd0f6917ac3 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 16 Sep 2019 14:29:10 +0300 Subject: [PATCH 810/841] MC-20139: Wishlist Items of customers not displaying on admin for secondary store --- .../ResourceModel/Item/Collection/{Grid.php => GridTest.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/{Grid.php => GridTest.php} (100%) diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/GridTest.php similarity index 100% rename from dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php rename to dev/tests/integration/testsuite/Magento/Wishlist/Model/ResourceModel/Item/Collection/GridTest.php From 6f4df9be6a8f2a43b08b3831821d01fe6f1e4c2f Mon Sep 17 00:00:00 2001 From: Mastiuhin Olexandr <mastiuhin.olexandr@transoftgroup.com> Date: Mon, 16 Sep 2019 14:30:39 +0300 Subject: [PATCH 811/841] MC-19872: Out-of-Stock Threshold can not be negative when Backorders are allowed --- .../Initialization/StockDataFilter.php | 28 +- ...inBackorderAllowedAddProductToCartTest.xml | 2 - .../Initialization/StockDataFilterTest.php | 57 ++- .../Model/ResourceModel/Stock/Item.php | 43 +- .../Model/StockStateProvider.php | 33 +- .../Model/System/Config/Backend/Minqty.php | 21 +- .../Unit/Model/StockStateProviderTest.php | 223 +++++++++++ .../Model/ResourceModel/Stock/ItemTest.php | 376 ++++++++++++++++++ .../System/Config/Backend/MinqtyTest.php | 67 ++++ 9 files changed, 812 insertions(+), 38 deletions(-) create mode 100644 app/code/Magento/CatalogInventory/Test/Unit/Model/StockStateProviderTest.php create mode 100644 dev/tests/integration/testsuite/Magento/CatalogInventory/Model/ResourceModel/Stock/ItemTest.php create mode 100644 dev/tests/integration/testsuite/Magento/CatalogInventory/Model/System/Config/Backend/MinqtyTest.php diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php index f7e69bc72ea18..47324c5b70908 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php @@ -7,6 +7,7 @@ use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\CatalogInventory\Model\Stock; /** * Class StockDataFilter @@ -60,8 +61,8 @@ public function filter(array $stockData) $stockData['qty'] = self::MAX_QTY_VALUE; } - if (isset($stockData['min_qty']) && (int)$stockData['min_qty'] < 0) { - $stockData['min_qty'] = 0; + if (isset($stockData['min_qty'])) { + $stockData['min_qty'] = $this->purifyMinQty($stockData['min_qty'], $stockData['backorders']); } if (!isset($stockData['is_decimal_divided']) || $stockData['is_qty_decimal'] == 0) { @@ -70,4 +71,27 @@ public function filter(array $stockData) return $stockData; } + + /** + * Purifies min_qty. + * + * @param int $minQty + * @param int $backOrders + * @return float + */ + private function purifyMinQty(int $minQty, int $backOrders): float + { + /** + * As described in the documentation if the Backorders Option is disabled + * it is recommended to set the Out Of Stock Threshold to a positive number. + * That's why to clarify the logic to the end user the code below prevent him to set a negative number so such + * a number will turn to zero. + * @see https://docs.magento.com/m2/ce/user_guide/catalog/inventory-backorders.html + */ + if ($backOrders === Stock::BACKORDERS_NO && $minQty < 0) { + $minQty = 0; + } + + return (float)$minQty; + } } diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml index 88c524eff387c..679cab4159ebd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminBackorderAllowedAddProductToCartTest.xml @@ -23,13 +23,11 @@ <createData entity="SimpleProductInStockQuantityZero" stepKey="createProduct"/> <!-- Configure Magento to show out of stock products and to allow backorders --> - <magentoCLI command="config:set {{CatalogInventoryOptionsShowOutOfStockEnable.path}} {{CatalogInventoryOptionsShowOutOfStockEnable.value}}" stepKey="setConfigShowOutOfStockTrue"/> <magentoCLI command="config:set {{CatalogInventoryItemOptionsBackordersEnable.path}} {{CatalogInventoryItemOptionsBackordersEnable.value}}" stepKey="setConfigAllowBackordersTrue"/> </before> <after> <!-- Set Magento back to default configuration --> - <magentoCLI command="config:set {{CatalogInventoryOptionsShowOutOfStockDisable.path}} {{CatalogInventoryOptionsShowOutOfStockDisable.value}}" stepKey="setConfigShowOutOfStockFalse"/> <magentoCLI command="config:set {{CatalogInventoryItemOptionsBackordersDisable.path}} {{CatalogInventoryItemOptionsBackordersDisable.value}}" stepKey="setConfigAllowBackordersFalse"/> <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> </after> diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php index 0214de8120bae..cb23d0c0a9a24 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/StockDataFilterTest.php @@ -6,11 +6,15 @@ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; +use Magento\CatalogInventory\Model\Stock; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\CatalogInventory\Model\Configuration; +use PHPUnit\Framework\TestCase; /** - * Class StockDataFilterTest + * StockDataFilter test. */ -class StockDataFilterTest extends \PHPUnit\Framework\TestCase +class StockDataFilterTest extends TestCase { /** * @var \PHPUnit_Framework_MockObject_MockObject @@ -27,17 +31,23 @@ class StockDataFilterTest extends \PHPUnit\Framework\TestCase */ protected $stockDataFilter; - /** @var \PHPUnit_Framework_MockObject_MockObject */ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ protected $stockConfiguration; + /** + * @inheritdoc + */ protected function setUp() { - $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); - $this->scopeConfigMock->expects($this->any())->method('getValue')->will($this->returnValue(1)); + $this->scopeConfigMock->method('getValue') + ->will($this->returnValue(1)); $this->stockConfiguration = $this->createPartialMock( - \Magento\CatalogInventory\Model\Configuration::class, + Configuration::class, ['getManageStock'] ); @@ -45,8 +55,11 @@ protected function setUp() } /** + * Tests filter method. + * * @param array $inputStockData * @param array $outputStockData + * @return void * * @covers \Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter::filter * @dataProvider filterDataProvider @@ -54,8 +67,7 @@ protected function setUp() public function testFilter(array $inputStockData, array $outputStockData) { if (isset($inputStockData['use_config_manage_stock']) && $inputStockData['use_config_manage_stock'] === 1) { - $this->stockConfiguration->expects($this->once()) - ->method('getManageStock') + $this->stockConfiguration->method('getManageStock') ->will($this->returnValue($outputStockData['manage_stock'])); } @@ -93,8 +105,13 @@ public function filterDataProvider() ], ], 'case4' => [ - 'inputStockData' => ['min_qty' => -1], - 'outputStockData' => ['min_qty' => 0, 'is_decimal_divided' => 0, 'use_config_manage_stock' => 0], + 'inputStockData' => ['min_qty' => -1, 'backorders' => Stock::BACKORDERS_NO], + 'outputStockData' => [ + 'min_qty' => 0, + 'is_decimal_divided' => 0, + 'use_config_manage_stock' => 0, + 'backorders' => Stock::BACKORDERS_NO, + ], ], 'case5' => [ 'inputStockData' => ['is_qty_decimal' => 0], @@ -103,7 +120,25 @@ public function filterDataProvider() 'is_decimal_divided' => 0, 'use_config_manage_stock' => 0, ], - ] + ], + 'case6' => [ + 'inputStockData' => ['min_qty' => -1, 'backorders' => Stock::BACKORDERS_YES_NONOTIFY], + 'outputStockData' => [ + 'min_qty' => -1, + 'is_decimal_divided' => 0, + 'use_config_manage_stock' => 0, + 'backorders' => Stock::BACKORDERS_YES_NONOTIFY, + ], + ], + 'case7' => [ + 'inputStockData' => ['min_qty' => -1, 'backorders' => Stock::BACKORDERS_YES_NOTIFY], + 'outputStockData' => [ + 'min_qty' => -1, + 'is_decimal_divided' => 0, + 'use_config_manage_stock' => 0, + 'backorders' => Stock::BACKORDERS_YES_NOTIFY, + ], + ], ]; } } diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php index edccad60231ec..3a214bd8cd7cb 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php @@ -14,6 +14,7 @@ use Magento\Framework\DB\Select; use Magento\Framework\App\ObjectManager; use Magento\Framework\Stdlib\DateTime\DateTime; +use Zend_Db_Expr; /** * Stock item resource model @@ -183,16 +184,12 @@ public function updateSetOutOfStock(int $websiteId) 'is_in_stock = ' . Stock::STOCK_IN_STOCK, '(use_config_manage_stock = 1 AND 1 = ' . $this->stockConfiguration->getManageStock() . ')' . ' OR (use_config_manage_stock = 0 AND manage_stock = 1)', - '(use_config_min_qty = 1 AND qty <= ' . $this->stockConfiguration->getMinQty() . ')' - . ' OR (use_config_min_qty = 0 AND qty <= min_qty)', + '(' . $this->getBackordersExpr() .' = 0 AND qty <= ' . $this->getMinQtyExpr() . ')' + . ' OR (' . $this->getBackordersExpr() .' != 0 AND ' + . $this->getMinQtyExpr() . ' != 0 AND qty <= ' . $this->getMinQtyExpr() . ')', 'product_id IN (' . $select->assemble() . ')', ]; - $backordersWhere = '(use_config_backorders = 0 AND backorders = ' . Stock::BACKORDERS_NO . ')'; - if (Stock::BACKORDERS_NO == $this->stockConfiguration->getBackorders()) { - $where[] = $backordersWhere . ' OR use_config_backorders = 1'; - } else { - $where[] = $backordersWhere; - } + $connection->update($this->getMainTable(), $values, $where); $this->stockIndexerProcessor->markIndexerAsInvalid(); @@ -215,8 +212,8 @@ public function updateSetInStock(int $websiteId) $where = [ 'website_id = ' . $websiteId, 'stock_status_changed_auto = 1', - '(use_config_min_qty = 1 AND qty > ' . $this->stockConfiguration->getMinQty() . ')' - . ' OR (use_config_min_qty = 0 AND qty > min_qty)', + '(qty > ' . $this->getMinQtyExpr() . ')' + . ' OR (' . $this->getBackordersExpr() . ' != 0 AND ' . $this->getMinQtyExpr() . ' = 0)', // If infinite 'product_id IN (' . $select->assemble() . ')', ]; $manageStockWhere = '(use_config_manage_stock = 0 AND manage_stock = 1)'; @@ -304,12 +301,12 @@ public function getBackordersExpr(string $tableAlias = ''): \Zend_Db_Expr } /** - * Get Minimum Sale Quantity Expression + * Get Minimum Sale Quantity Expression. * * @param string $tableAlias - * @return \Zend_Db_Expr + * @return Zend_Db_Expr */ - public function getMinSaleQtyExpr(string $tableAlias = ''): \Zend_Db_Expr + public function getMinSaleQtyExpr(string $tableAlias = ''): Zend_Db_Expr { if ($tableAlias) { $tableAlias .= '.'; @@ -323,6 +320,26 @@ public function getMinSaleQtyExpr(string $tableAlias = ''): \Zend_Db_Expr return $itemMinSaleQty; } + /** + * Get Min Qty Expression + * + * @param string $tableAlias + * @return Zend_Db_Expr + */ + public function getMinQtyExpr(string $tableAlias = ''): Zend_Db_Expr + { + if ($tableAlias) { + $tableAlias .= '.'; + } + $itemBackorders = $this->getConnection()->getCheckSql( + $tableAlias . 'use_config_min_qty = 1', + $this->stockConfiguration->getMinQty(), + $tableAlias . 'min_qty' + ); + + return $itemBackorders; + } + /** * Build select for products with types from config * diff --git a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php index 6851b05aa56a6..74271cdd97bf8 100644 --- a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php +++ b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php @@ -72,14 +72,31 @@ public function __construct( */ public function verifyStock(StockItemInterface $stockItem) { + // Manage stock, but qty is null if ($stockItem->getQty() === null && $stockItem->getManageStock()) { return false; } + + // Backorders are not allowed and qty reached min qty if ($stockItem->getBackorders() == StockItemInterface::BACKORDERS_NO && $stockItem->getQty() <= $stockItem->getMinQty() ) { return false; } + + $backordersAllowed = [Stock::BACKORDERS_YES_NONOTIFY, Stock::BACKORDERS_YES_NOTIFY]; + if (in_array($stockItem->getBackorders(), $backordersAllowed)) { + // Infinite - let it be In stock + if ($stockItem->getMinQty() == 0) { + return true; + } + + // qty reached min qty - let it stand Out Of Stock + if ($stockItem->getQty() <= $stockItem->getMinQty()) { + return false; + } + } + return true; } @@ -245,15 +262,17 @@ public function checkQty(StockItemInterface $stockItem, $qty) if (!$stockItem->getManageStock()) { return true; } + + $backordersAllowed = [Stock::BACKORDERS_YES_NONOTIFY, Stock::BACKORDERS_YES_NOTIFY]; + // Infinite check + if ($stockItem->getMinQty() == 0 && in_array($stockItem->getBackorders(), $backordersAllowed)) { + return true; + } + if ($stockItem->getQty() - $stockItem->getMinQty() - $qty < 0) { - switch ($stockItem->getBackorders()) { - case \Magento\CatalogInventory\Model\Stock::BACKORDERS_YES_NONOTIFY: - case \Magento\CatalogInventory\Model\Stock::BACKORDERS_YES_NOTIFY: - break; - default: - return false; - } + return false; } + return true; } diff --git a/app/code/Magento/CatalogInventory/Model/System/Config/Backend/Minqty.php b/app/code/Magento/CatalogInventory/Model/System/Config/Backend/Minqty.php index f49d41b5dd656..268f1846161d4 100644 --- a/app/code/Magento/CatalogInventory/Model/System/Config/Backend/Minqty.php +++ b/app/code/Magento/CatalogInventory/Model/System/Config/Backend/Minqty.php @@ -6,21 +6,36 @@ namespace Magento\CatalogInventory\Model\System\Config\Backend; +use Magento\CatalogInventory\Model\Stock; + /** - * Minimum product qty backend model + * Minimum product qty backend model. */ class Minqty extends \Magento\Framework\App\Config\Value { /** - * Validate minimum product qty value + * Validate minimum product qty value. * * @return $this */ public function beforeSave() { parent::beforeSave(); - $minQty = (int) $this->getValue() >= 0 ? (int) $this->getValue() : (int) $this->getOldValue(); + $minQty = (float)$this->getValue(); + + /** + * As described in the documentation if the Backorders Option is disabled + * it is recommended to set the Out Of Stock Threshold to a positive number. + * That's why to clarify the logic to the end user the code below prevent him to set a negative number so such + * a number will turn to zero. + * @see https://docs.magento.com/m2/ce/user_guide/catalog/inventory-backorders.html + */ + if ($this->getFieldsetDataValue("backorders") == Stock::BACKORDERS_NO && $minQty < 0) { + $minQty = 0; + } + $this->setValue((string) $minQty); + return $this; } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/StockStateProviderTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/StockStateProviderTest.php new file mode 100644 index 0000000000000..942d77063a8e3 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/StockStateProviderTest.php @@ -0,0 +1,223 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Test\Unit\Model; + +use PHPUnit\Framework\TestCase; +use Magento\CatalogInventory\Model\StockStateProvider; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Model\Stock; + +/** + * StockRegistry test. + */ +class StockStateProviderTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var StockStateProvider + */ + private $model; + + /** + * @var StockItemInterface|\PHPUnit\Framework\MockObject\MockObject + */ + private $stockItem; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + + $this->stockItem = $this->getMockBuilder(StockItemInterface::class) + ->getMock(); + + $this->model = $this->objectManager->getObject(StockStateProvider::class); + } + + /** + * Tests verifyStock method. + * + * @param int $qty + * @param int $backOrders + * @param int $minQty + * @param int $manageStock + * @param int $expected + * + * @return void + * + * @dataProvider stockItemDataProvider + * @covers \Magento\CatalogInventory\Model\StockStateProvider::verifyStock + */ + public function testVerifyStock( + ?int $qty, + ?int $backOrders, + ?int $minQty, + ?int $manageStock, + bool $expected + ): void { + $this->stockItem->method('getQty') + ->willReturn($qty); + $this->stockItem->method('getBackOrders') + ->willReturn($backOrders); + $this->stockItem->method('getMinQty') + ->willReturn($minQty); + $this->stockItem->method('getManageStock') + ->willReturn($manageStock); + + $result = $this->model->verifyStock($this->stockItem); + + self::assertEquals($expected, $result); + } + + /** + * StockItem data provider. + * + * @return array + */ + public function stockItemDataProvider(): array + { + return [ + 'qty_is_null_manage_stock_on' => [ + 'qty' => null, + 'backorders' => null, + 'min_qty' => null, + 'manage_stock' => 1, + 'expected' => false, + ], + 'qty_reached_threshold_without_backorders' => [ + 'qty' => 3, + 'backorders' => Stock::BACKORDERS_NO, + 'min_qty' => 3, + 'manage_stock' => 1, + 'expected' => false, + ], + 'backorders_are_ininite' => [ + 'qty' => -100, + 'backorders' => Stock::BACKORDERS_YES_NONOTIFY, + 'min_qty' => 0, + 'manage_stock' => 1, + 'expected' => true, + ], + 'limited_backorders_and_qty_reached_threshold' => [ + 'qty' => -100, + 'backorders' => Stock::BACKORDERS_YES_NONOTIFY, + 'min_qty' => -100, + 'manage_stock' => 1, + 'expected' => false, + ], + 'qty_not_yet_reached_threshold_1' => [ + 'qty' => -99, + 'backorders' => Stock::BACKORDERS_YES_NONOTIFY, + 'min_qty' => -100, + 'manage_stock' => 1, + 'expected' => true, + ], + 'qty_not_yet_reached_threshold_2' => [ + 'qty' => 1, + 'backorders' => Stock::BACKORDERS_NO, + 'min_qty' => 0, + 'manage_stock' => 1, + 'expected' => true, + ], + ]; + } + + /** + * Tests checkQty method. + * + * @return void + * + * @dataProvider stockItemAndQtyDataProvider + * @covers \Magento\CatalogInventory\Model\StockStateProvider::verifyStock + */ + public function testCheckQty( + bool $manageStock, + int $qty, + int $minQty, + int $backOrders, + int $orderQty, + bool $expected + ): void { + $this->stockItem->method('getManageStock') + ->willReturn($manageStock); + $this->stockItem->method('getQty') + ->willReturn($qty); + $this->stockItem->method('getMinQty') + ->willReturn($minQty); + $this->stockItem->method('getBackOrders') + ->willReturn($backOrders); + + $result = $this->model->checkQty($this->stockItem, $orderQty); + + self::assertEquals($expected, $result); + } + + /** + * StockItem and qty data provider. + * + * @return array + */ + public function stockItemAndQtyDataProvider(): array + { + return [ + 'disabled_manage_stock' => [ + 'manage_stock' => false, + 'qty' => 0, + 'min_qty' => 0, + 'backorders' => 0, + 'order_qty' => 0, + 'expected' => true, + ], + 'infinite_backorders' => [ + 'manage_stock' => true, + 'qty' => -100, + 'min_qty' => 0, + 'backorders' => Stock::BACKORDERS_YES_NONOTIFY, + 'order_qty' => 100, + 'expected' => true, + ], + 'qty_reached_threshold' => [ + 'manage_stock' => true, + 'qty' => -100, + 'min_qty' => -100, + 'backorders' => Stock::BACKORDERS_YES_NOTIFY, + 'order_qty' => 1, + 'expected' => false, + ], + 'qty_yet_not_reached_threshold' => [ + 'manage_stock' => true, + 'qty' => -100, + 'min_qty' => -100, + 'backorders' => Stock::BACKORDERS_YES_NOTIFY, + 'order_qty' => 1, + 'expected' => false, + ] + ]; + } + + /** + * Tests checkQty method when check is not applicable. + * + * @return void + */ + public function testCheckQtyWhenCheckIsNotApplicable(): void + { + $model = $this->objectManager->getObject(StockStateProvider::class, ['qtyCheckApplicable' => false]); + + $result = $model->checkQty($this->stockItem, 3); + + self::assertTrue($result); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/ResourceModel/Stock/ItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/ResourceModel/Stock/ItemTest.php new file mode 100644 index 0000000000000..460f43d816e35 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/ResourceModel/Stock/ItemTest.php @@ -0,0 +1,376 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Model\ResourceModel\Stock; + +use PHPUnit\Framework\TestCase; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogInventory\Api\StockItemRepositoryInterface; +use Magento\CatalogInventory\Api\StockItemCriteriaInterfaceFactory; +use Magento\CatalogInventory\Api\Data\StockItemInterface; +use Magento\CatalogInventory\Api\StockConfigurationInterface; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\CatalogInventory\Model\Stock; +use Magento\TestFramework\App\Config; +use Magento\Store\Model\ScopeInterface; +use Magento\CatalogInventory\Model\Configuration; + +/** + * Item test. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class ItemTest extends TestCase +{ + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Item + */ + private $stockItemResourceModel; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * @var StockItemRepositoryInterface + */ + private $stockItemRepository; + + /** + * @var StockItemCriteriaInterfaceFactory + */ + private $stockItemCriteriaFactory; + + /** + * @var StockConfigurationInterface + */ + private $stockConfiguration; + + /** + * @var Config + */ + private $config; + + /** + * Saved Stock Status Item data. + * + * @var array + */ + private $stockStatusData; + + /** + * Saved system config data. + * + * @var array + */ + private $configData; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + + $this->stockItemResourceModel = $this->objectManager->get(Item::class); + $this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $this->stockItemRepository = $this->objectManager->get(StockItemRepositoryInterface::class); + $this->stockItemCriteriaFactory = $this->objectManager->get(StockItemCriteriaInterfaceFactory::class); + $this->stockConfiguration = $this->objectManager->get(StockConfigurationInterface::class); + $this->config = $this->objectManager->get(Config::class); + + $this->storeSystemConfig(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->restoreSystemConfig(); + } + + /** + * Tests updateSetOutOfStock method. + * + * @return void + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testUpdateSetOutOfStock(): void + { + $stockItem = $this->getStockItem(1); + $this->saveStockItemData($stockItem); + $this->storeSystemConfig(); + + foreach ($this->stockStatusVariations() as $variation) { + /** + * Check when Stock Item use it's own configuration of backorders. + */ + $this->configureStockItem($stockItem, $variation); + $this->stockItemResourceModel->updateSetOutOfStock($this->stockConfiguration->getDefaultScopeId()); + $stockItem = $this->getStockItem(1); + + self::assertEquals($variation['is_in_stock'], $stockItem->getIsInStock(), $variation['message']); + $stockItem = $this->resetStockItem($stockItem); + + /** + * Check when Stock Item use system configuration of backorders. + */ + $this->configureStockItemWithSystemConfig($stockItem, $variation); + $this->stockItemResourceModel->updateSetOutOfStock($this->stockConfiguration->getDefaultScopeId()); + $stockItem = $this->getStockItem(1); + + self::assertEquals($variation['is_in_stock'], $stockItem->getIsInStock(), $variation['message']); + $stockItem = $this->resetStockItem($stockItem); + $this->restoreSystemConfig(); + } + } + + /** + * Tests updateSetInOfStock method. + * + * @return void + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php + */ + public function testUpdateSetInStock(): void + { + $product = $this->productRepository->get('simple-out-of-stock'); + $stockItem = $this->getStockItem((int)$product->getId()); + $this->saveStockItemData($stockItem); + $this->storeSystemConfig(); + + foreach ($this->stockStatusVariations() as $variation) { + /** + * Check when Stock Item use it's own configuration of backorders. + */ + $stockItem->setStockStatusChangedAutomaticallyFlag(true); + $this->configureStockItem($stockItem, $variation); + $this->stockItemResourceModel->updateSetInStock($this->stockConfiguration->getDefaultScopeId()); + $stockItem = $this->getStockItem((int)$product->getId()); + + self::assertEquals($variation['is_in_stock'], $stockItem->getIsInStock(), $variation['message']); + $stockItem = $this->resetStockItem($stockItem); + + /** + * Check when Stock Item use the system configuration of backorders. + */ + $stockItem->setStockStatusChangedAuto(1); + $this->configureStockItemWithSystemConfig($stockItem, $variation); + $this->stockItemResourceModel->updateSetInStock($this->stockConfiguration->getDefaultScopeId()); + $stockItem = $this->getStockItem((int)$product->getId()); + + self::assertEquals($variation['is_in_stock'], $stockItem->getIsInStock(), $variation['message']); + $stockItem = $this->resetStockItem($stockItem); + $this->restoreSystemConfig(); + } + } + + /** + * Configure backorders feature for Stock Item. + * + * @param StockItemInterface $stockItem + * @param array $config + * @return void + */ + private function configureStockItem(StockItemInterface $stockItem, array $config): void + { + /** + * Configuring Stock Item to use it's own configuration. + */ + $stockItem->setUseConfigBackorders(0); + $stockItem->setUseConfigMinQty(0); + $stockItem->setQty($config['qty']); + $stockItem->setMinQty($config['min_qty']); + $stockItem->setBackorders($config['backorders']); + + $this->stockItemRepository->save($stockItem); + } + + /** + * Configure backorders feature using the system configuration for Stock Item. + * + * @param StockItemInterface $stockItem + * @param array $config + * @return void + */ + private function configureStockItemWithSystemConfig(StockItemInterface $stockItem, array $config): void + { + /** + * Configuring Stock Item to use the system configuration. + */ + $stockItem->setUseConfigBackorders(1); + $stockItem->setUseConfigMinQty(1); + + $this->config->setValue( + Configuration::XML_PATH_BACKORDERS, + $config['backorders'], + ScopeInterface::SCOPE_STORE + ); + $this->config->setValue( + Configuration::XML_PATH_MIN_QTY, + $config['min_qty'], + ScopeInterface::SCOPE_STORE + ); + + $stockItem->setQty($config['qty']); + + $this->stockItemRepository->save($stockItem); + } + + /** + * Stock status variations. + * + * @return array + */ + private function stockStatusVariations(): array + { + return [ + // Quantity has not reached Threshold + [ + 'qty' => 3, + 'min_qty' => 2, + 'backorders' => Stock::BACKORDERS_NO, + 'is_in_stock' => true, + 'message' => "Stock status should be In Stock - v.1", + ], + // Quantity has reached Threshold + [ + 'qty' => 3, + 'min_qty' => 3, + 'backorders' => Stock::BACKORDERS_NO, + 'is_in_stock' => false, + 'message' => "Stock status should be Out of Stock - v.2", + ], + // Infinite backorders + [ + 'qty' => -100, + 'min_qty' => 0, + 'backorders' => Stock::BACKORDERS_YES_NOTIFY, + 'is_in_stock' => true, + 'message' => "Stock status should be In Stock for infinite backorders - v.3", + ], + // Quantity has not reached Threshold's negative value + [ + 'qty' => -99, + 'min_qty' => -100, + 'backorders' => Stock::BACKORDERS_YES_NOTIFY, + 'is_in_stock' => true, + 'message' => "Stock status should be In Stock - v.4", + ], + // Quantity has reached Threshold's negative value + [ + 'qty' => -100, + 'min_qty' => -99, + 'backorders' => Stock::BACKORDERS_YES_NOTIFY, + 'is_in_stock' => false, + 'message' => "Stock status should be Out of Stock - v.5", + ], + ]; + } + + /** + * Stores Stock Item values. + * + * @param StockItemInterface $stockItem + * @return void + */ + private function saveStockItemData(StockItemInterface $stockItem): void + { + $this->stockStatusData = $stockItem->getData(); + } + + /** + * Resets Stock Item to previous saved values and prepare for new test variation. + * + * @param StockItemInterface $stockItem + * @return StockItemInterface + */ + private function resetStockItem(StockItemInterface $stockItem): StockItemInterface + { + $stockItem->setData($this->stockStatusData); + + return $this->stockItemRepository->save($stockItem); + } + + /** + * Get Stock Item by product id. + * + * @param int $productId + * @param int|null $scope + * @return StockItemInterface + * @throws NoSuchEntityException + */ + private function getStockItem(int $productId, ?int $scope = null): StockItemInterface + { + $scope = $scope ?? $this->stockConfiguration->getDefaultScopeId(); + $stockItemCriteria = $this->stockItemCriteriaFactory->create(); + $stockItemCriteria->setScopeFilter($scope); + $stockItemCriteria->setProductsFilter([$productId]); + $stockItems = $this->stockItemRepository->getList($stockItemCriteria); + $stockItems = $stockItems->getItems(); + + if (empty($stockItems)) { + throw new NoSuchEntityException(); + } + + $stockItem = reset($stockItems); + + return $stockItem; + } + + /** + * Stores system configuration. + * + * @return void + */ + private function storeSystemConfig(): void + { + /** + * Save system configuration data. + */ + $backorders = $this->config->getValue( + Configuration::XML_PATH_BACKORDERS, + ScopeInterface::SCOPE_STORE + ); + $minQty = $this->config->getValue(Configuration::XML_PATH_MIN_QTY, ScopeInterface::SCOPE_STORE); + $this->configData = [ + 'backorders' => $backorders, + 'min_qty' => $minQty, + ]; + } + + /** + * Restores system configuration. + * + * @return void + */ + private function restoreSystemConfig(): void + { + /** + * Turn back system configuration. + */ + $this->config->setValue( + Configuration::XML_PATH_BACKORDERS, + $this->configData['backorders'], + ScopeInterface::SCOPE_STORE + ); + $this->config->setValue( + Configuration::XML_PATH_MIN_QTY, + $this->configData['min_qty'], + ScopeInterface::SCOPE_STORE + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/System/Config/Backend/MinqtyTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/System/Config/Backend/MinqtyTest.php new file mode 100644 index 0000000000000..c053d38fea1ff --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/System/Config/Backend/MinqtyTest.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogInventory\Model\System\Config\Backend; + +use PHPUnit\Framework\TestCase; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\CatalogInventory\Model\Stock; + +/** + * Minqty test. + */ +class MinqtyTest extends TestCase +{ + /** + * @var Minqty + */ + private $minQtyConfig; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->minQtyConfig = $objectManager->create(Minqty::class); + $this->minQtyConfig->setPath('cataloginventory/item_options/min_qty'); + } + + /** + * Tests beforeSave method. + * + * @param string $value + * @param array $fieldSetData + * @param string $expected + * @return void + * + * @dataProvider minQtyConfigDataProvider + */ + public function testBeforeSave(string $value, array $fieldSetData, string $expected): void + { + $this->minQtyConfig->setData('fieldset_data', $fieldSetData); + $this->minQtyConfig->setValue($value); + $this->minQtyConfig->beforeSave(); + $this->assertEquals($expected, $this->minQtyConfig->getValue()); + } + + /** + * Minqty config data provider. + * + * @return array + */ + public function minQtyConfigDataProvider(): array + { + return [ + 'straight' => ['3', ['backorders' => Stock::BACKORDERS_NO], '3'], + 'straight2' => ['3.5', ['backorders' => Stock::BACKORDERS_NO], '3.5'], + 'negative_value_disabled_backorders' => ['-3', ['backorders' => Stock::BACKORDERS_NO], '0'], + 'negative_value_enabled_backorders' => ['-3', ['backorders' => Stock::BACKORDERS_YES_NOTIFY], '-3'], + 'negative_value_enabled_backorders2' => ['-3.05', ['backorders' => Stock::BACKORDERS_YES_NOTIFY], '-3.05'], + ]; + } +} From b2a27b8377dbf6fa0a20508a932711fa87ccfa54 Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Mon, 16 Sep 2019 14:44:22 +0300 Subject: [PATCH 812/841] MC-19527: The Elastic Search doesn't work with Automatic (equalize product counts) settings --- .../ResourceModel/Advanced/Collection.php | 3 +- .../ResourceModel/Fulltext/Collection.php | 22 +++--- .../ResourceModel/Advanced/CollectionTest.php | 52 +++++++++----- .../ResourceModel/Fulltext/CollectionTest.php | 67 +++++++++++++++---- .../Collection/SearchCriteriaResolver.php | 3 - .../Collection/SearchResultApplier.php | 43 +++++++++++- .../Collection/SearchCriteriaResolverTest.php | 2 +- 7 files changed, 145 insertions(+), 47 deletions(-) diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index 1946dd35b8d37..595bc12ca956a 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -391,7 +391,8 @@ private function getSearchResultApplier(SearchResultInterface $searchResult): Se 'collection' => $this, 'searchResult' => $searchResult, /** This variable sets by serOrder method, but doesn't have a getter method. */ - 'orders' => $this->_orders + 'orders' => $this->_orders, + 'size' => $this->getPageSize(), ] ); } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php index 4f84f3868c6a3..14305359a71b3 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php @@ -485,12 +485,12 @@ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface { return $this->searchCriteriaResolverFactory->create( [ - 'builder' => $this->getSearchCriteriaBuilder(), - 'collection' => $this, - 'searchRequestName' => $this->searchRequestName, - 'currentPage' => $this->_curPage, - 'size' => $this->getPageSize(), - 'orders' => $this->searchOrders, + 'builder' => $this->getSearchCriteriaBuilder(), + 'collection' => $this, + 'searchRequestName' => $this->searchRequestName, + 'currentPage' => (int)$this->_curPage, + 'size' => $this->getPageSize(), + 'orders' => $this->searchOrders, ] ); } @@ -505,10 +505,12 @@ private function getSearchResultApplier(SearchResultInterface $searchResult): Se { return $this->searchResultApplierFactory->create( [ - 'collection' => $this, - 'searchResult' => $searchResult, - /** This variable sets by serOrder method, but doesn't have a getter method. */ - 'orders' => $this->_orders, + 'collection' => $this, + 'searchResult' => $searchResult, + /** This variable sets by serOrder method, but doesn't have a getter method. */ + 'orders' => $this->_orders, + 'size' => $this->getPageSize(), + 'currentPage' => (int)$this->_curPage, ] ); } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php index 683070c286239..f5e5a34047aff 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php @@ -14,6 +14,7 @@ use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory; +use PHPUnit\Framework\MockObject\MockObject; /** * Tests Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection @@ -35,32 +36,37 @@ class CollectionTest extends BaseCollection private $advancedCollection; /** - * @var \Magento\Framework\Api\FilterBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Api\FilterBuilder|MockObject */ private $filterBuilder; /** - * @var \Magento\Framework\Api\Search\SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Api\Search\SearchCriteriaBuilder|MockObject */ private $criteriaBuilder; /** - * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory|MockObject */ private $temporaryStorageFactory; /** - * @var \Magento\Search\Api\SearchInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Search\Api\SearchInterface|MockObject */ private $search; /** - * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Eav\Model\Config|MockObject */ private $eavConfig; /** - * setUp method for CollectionTest + * @var SearchResultApplierFactory|MockObject + */ + private $searchResultApplierFactory; + + /** + * @inheritdoc */ protected function setUp() { @@ -97,17 +103,10 @@ protected function setUp() ->method('create') ->willReturn($searchCriteriaResolver); - $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class) - ->disableOriginalConstructor() - ->setMethods(['apply']) - ->getMockForAbstractClass(); - $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class) + $this->searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $searchResultApplierFactory->expects($this->any()) - ->method('create') - ->willReturn($searchResultApplier); $totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class) ->disableOriginalConstructor() @@ -134,12 +133,15 @@ protected function setUp() 'productLimitationFactory' => $productLimitationFactoryMock, 'collectionProvider' => null, 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory, - 'searchResultApplierFactory' => $searchResultApplierFactory, + 'searchResultApplierFactory' => $this->searchResultApplierFactory, 'totalRecordsResolverFactory' => $totalRecordsResolverFactory ] ); } + /** + * Test to Load data with filter in place + */ public function testLoadWithFilterNoFilters() { $this->advancedCollection->loadWithFilter(); @@ -150,6 +152,7 @@ public function testLoadWithFilterNoFilters() */ public function testLike() { + $pageSize = 10; $attributeCode = 'description'; $attributeCodeId = 42; $attribute = $this->createMock(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class); @@ -168,6 +171,22 @@ public function testLike() $searchResult = $this->createMock(\Magento\Framework\Api\Search\SearchResultInterface::class); $this->search->expects($this->once())->method('search')->willReturn($searchResult); + $this->advancedCollection->setPageSize($pageSize); + $this->advancedCollection->setCurPage(0); + + $searchResultApplier = $this->createMock(SearchResultApplierInterface::class); + $this->searchResultApplierFactory->expects($this->once()) + ->method('create') + ->with( + [ + 'collection' => $this->advancedCollection, + 'searchResult' => $searchResult, + 'orders' => [], + 'size' => $pageSize, + ] + ) + ->willReturn($searchResultApplier); + // addFieldsToFilter will load filters, // then loadWithFilter will trigger _renderFiltersBefore code in Advanced/Collection $this->assertSame( @@ -177,7 +196,7 @@ public function testLike() } /** - * @return \PHPUnit_Framework_MockObject_MockObject + * @return MockObject */ protected function getCriteriaBuilder() { @@ -185,6 +204,7 @@ protected function getCriteriaBuilder() ->setMethods(['addFilter', 'create', 'setRequestName']) ->disableOriginalConstructor() ->getMock(); + return $criteriaBuilder; } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php index 9170b81dc3182..9b4010cfae453 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php @@ -5,6 +5,7 @@ */ namespace Magento\CatalogSearch\Test\Unit\Model\ResourceModel\Fulltext; +use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory; @@ -12,11 +13,12 @@ use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface; use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface; use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection; +use PHPUnit\Framework\MockObject\MockObject; use Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory; -use PHPUnit_Framework_MockObject_MockObject as MockObject; -use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; /** + * Test class for Fulltext Collection + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CollectionTest extends BaseCollection @@ -27,12 +29,12 @@ class CollectionTest extends BaseCollection private $objectManager; /** - * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorage|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Search\Adapter\Mysql\TemporaryStorage|MockObject */ private $temporaryStorage; /** - * @var \Magento\Search\Api\SearchInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Search\Api\SearchInterface|MockObject */ private $search; @@ -61,6 +63,11 @@ class CollectionTest extends BaseCollection */ private $filterBuilder; + /** + * @var SearchResultApplierFactory|MockObject + */ + private $searchResultApplierFactory; + /** * @var \Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection */ @@ -72,7 +79,7 @@ class CollectionTest extends BaseCollection private $filter; /** - * setUp method for CollectionTest + * @inheritdoc */ protected function setUp() { @@ -115,17 +122,10 @@ protected function setUp() ->method('create') ->willReturn($searchCriteriaResolver); - $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class) - ->disableOriginalConstructor() - ->setMethods(['apply']) - ->getMockForAbstractClass(); - $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class) + $this->searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $searchResultApplierFactory->expects($this->any()) - ->method('create') - ->willReturn($searchResultApplier); $totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class) ->disableOriginalConstructor() @@ -148,7 +148,7 @@ protected function setUp() 'temporaryStorageFactory' => $temporaryStorageFactory, 'productLimitationFactory' => $productLimitationFactoryMock, 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory, - 'searchResultApplierFactory' => $searchResultApplierFactory, + 'searchResultApplierFactory' => $this->searchResultApplierFactory, 'totalRecordsResolverFactory' => $totalRecordsResolverFactory, ] ); @@ -161,6 +161,9 @@ protected function setUp() $this->model->setFilterBuilder($this->filterBuilder); } + /** + * @inheritdoc + */ protected function tearDown() { $reflectionProperty = new \ReflectionProperty(\Magento\Framework\App\ObjectManager::class, '_instance'); @@ -168,16 +171,49 @@ protected function tearDown() $reflectionProperty->setValue(null); } + /** + * Test to Return field faceted data from faceted search result + */ public function testGetFacetedDataWithEmptyAggregations() { + $pageSize = 10; + $searchResult = $this->getMockBuilder(\Magento\Framework\Api\Search\SearchResultInterface::class) ->getMockForAbstractClass(); $this->search->expects($this->once()) ->method('search') ->willReturn($searchResult); + + $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class) + ->disableOriginalConstructor() + ->setMethods(['apply']) + ->getMockForAbstractClass(); + $this->searchResultApplierFactory->expects($this->any()) + ->method('create') + ->willReturn($searchResultApplier); + + $this->model->setPageSize($pageSize); + $this->model->setCurPage(0); + + $this->searchResultApplierFactory->expects($this->once()) + ->method('create') + ->with( + [ + 'collection' => $this->model, + 'searchResult' => $searchResult, + 'orders' => [], + 'size' => $pageSize, + 'currentPage' => 0, + ] + ) + ->willReturn($searchResultApplier); + $this->model->getFacetedData('field'); } + /** + * Test to Apply attribute filter to facet collection + */ public function testAddFieldToFilter() { $this->filter = $this->createFilter(); @@ -220,6 +256,7 @@ protected function getCriteriaBuilder() protected function getFilterBuilder() { $filterBuilder = $this->createMock(\Magento\Framework\Api\FilterBuilder::class); + return $filterBuilder; } @@ -241,6 +278,7 @@ protected function addFiltersToFilterBuilder(MockObject $filterBuilder, array $f ->with($value) ->willReturnSelf(); } + return $filterBuilder; } @@ -252,6 +290,7 @@ protected function createFilter() $filter = $this->getMockBuilder(\Magento\Framework\Api\Filter::class) ->disableOriginalConstructor() ->getMock(); + return $filter; } } diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php index 1c8885fec63be..ce88fc290e23c 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php @@ -76,9 +76,6 @@ public function __construct( */ public function resolve(): SearchCriteria { - if ($this->size !== 0) { - $this->builder->setPageSize($this->size); - } $searchCriteria = $this->builder->create(); $searchCriteria->setRequestName($this->searchRequestName); $searchCriteria->setSortOrders($this->orders); diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php index 3ae2d384782c3..aac396f238358 100644 --- a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php +++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php @@ -25,16 +25,32 @@ class SearchResultApplier implements SearchResultApplierInterface */ private $searchResult; + /** + * @var int + */ + private $size; + + /** + * @var int + */ + private $currentPage; + /** * @param Collection $collection * @param SearchResultInterface $searchResult + * @param int $size + * @param int $currentPage */ public function __construct( Collection $collection, - SearchResultInterface $searchResult + SearchResultInterface $searchResult, + int $size, + int $currentPage ) { $this->collection = $collection; $this->searchResult = $searchResult; + $this->size = $size; + $this->currentPage = $currentPage; } /** @@ -46,8 +62,10 @@ public function apply() $this->collection->getSelect()->where('NULL'); return; } + + $items = $this->sliceItems($this->searchResult->getItems(), $this->size, $this->currentPage); $ids = []; - foreach ($this->searchResult->getItems() as $item) { + foreach ($items as $item) { $ids[] = (int)$item->getId(); } $this->collection->setPageSize(null); @@ -56,4 +74,25 @@ public function apply() $this->collection->getSelect()->reset(\Magento\Framework\DB\Select::ORDER); $this->collection->getSelect()->order("FIELD(e.entity_id,$orderList)"); } + + /** + * Slice current items + * + * @param array $items + * @param int $size + * @param int $currentPage + * @return array + */ + private function sliceItems(array $items, int $size, int $currentPage): array + { + if ($size !== 0) { + $offset = ($currentPage - 1) * $size; + if ($offset < 0) { + $offset = 0; + } + $items = array_slice($items, $offset, $this->size); + } + + return $items; + } } diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php index fbf63630464f9..b2c0f5e341fc2 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverTest.php @@ -106,7 +106,7 @@ public function resolveSortOrderDataProvider() ], [ ['size' => 10, 'orders' => ['test' => 'ASC']], - ['size' => 10, 'orders' => ['test' => 'ASC']], + ['size' => null, 'orders' => ['test' => 'ASC']], ], ]; } From 579860a0773543402627fa3c0dc2e7cc33bb17d6 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Mon, 16 Sep 2019 14:46:24 +0300 Subject: [PATCH 813/841] MC-13641: Saving configurable product with custom product attribute (images as swatches) --- .../AdminProductFormConfigurationsSection.xml | 1 + ...uctWithAttributesImagesAndSwatchesTest.xml | 110 ++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index 1defecbc7c285..5d8174e4eb711 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -39,6 +39,7 @@ <element name="variationLabel" type="text" selector="//div[@data-index='configurable-matrix']/label"/> <element name="stepsWizardTitle" type="text" selector="div.content:not([style='display: none;']) .steps-wizard-title"/> <element name="attributeEntityByName" type="text" selector="//div[@class='attribute-entity']//div[normalize-space(.)='{{attributeLabel}}']" parameterized="true"/> + <element name="fileUploaderInput" type="file" selector="//input[@type='file' and @class='file-uploader-input']" /> </section> <section name="AdminConfigurableProductFormSection"> <element name="productWeight" type="input" selector=".admin__control-text[name='product[weight]']"/> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml new file mode 100644 index 0000000000000..0b435250cd867 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest"> + <annotations> + <features value="Catalog"/> + <stories value="Product attributes"/> + <title value="Saving configurable product with custom product attribute (images as swatches)"/> + <description value="Saving configurable product with custom product attribute (images as swatches)"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13641"/> + <group value="catalog"/> + </annotations> + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + + <!-- Create a new product attribute --> + <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="openProductAttributePage"/> + <click selector="{{AdminProductAttributeGridSection.createNewAttributeBtn}}" stepKey="createNewAttribute"/> + <!-- Set Catalog Input Type for Store Owner: Visual Swatch --> + <actionGroup ref="AdminFillProductAttributePropertiesActionGroup" stepKey="fillAttributeProperties"> + <argument name="attributeName" value="{{VisualSwatchProductAttribute.attribute_code}}"/> + <argument name="attributeType" value="{{VisualSwatchProductAttribute.frontend_input}}"/> + </actionGroup> + <!-- Add a few Swatches and add images to Manage Swatch (Values of Your Attribute) + 1. Set swatch #1 using the color picker --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddFirstSwatch"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickFirstSwatch"> + <argument name="index" value="0"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('1')}}" stepKey="clickChooseColor"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillFirstHex"> + <argument name="nthColorPicker" value="1"/> + <argument name="hexColor" value="e74c3c"/> + </actionGroup> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="red" stepKey="fillFirstAdminField"/> + <!-- Set swatch #2 using upload file --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSecondSwatch"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch2"> + <argument name="index" value="1"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthUploadFile('2')}}" stepKey="clickUploadFile2"/> + <attachFile selector="input[name='datafile']" userInput="{{placeholderSmallImage.file}}" stepKey="attachFile"/> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('1')}}" userInput="adobe-small" stepKey="fillAdminLabel"/> + <!-- Set Scope: Global in Advanced Attribute Properties --> + <click selector="{{AttributePropertiesSection.AdvancedProperties}}" stepKey="expandAdvancedProperties"/> + <selectOption selector="{{AttributePropertiesSection.Scope}}" userInput="1" stepKey="selectGlobalScope"/> + <!-- Click "Save Attribute" button --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccessMessage"/> + </before> + <after> + <!-- Delete product attribute and clear grid filter --> + <actionGroup ref="deleteProductAttributeByAttributeCode" stepKey="deleteProductAttribute"> + <argument name="ProductAttributeCode" value="{{VisualSwatchProductAttribute.attribute_code}}"/> + </actionGroup> + <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="clearAttributesGridFilter"/> + <!--Clear products grid filter--> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductIndexPage"/> + <actionGroup ref="AdminGridFilterResetActionGroup" stepKey="clearProductsGridFilter"/> + <!-- Admin logout --> + <actionGroup ref="logout" stepKey="adminLogout"/> + </after> + + <!-- Add created product attribute to the Default set --> + <actionGroup ref="AdminOpenAttributeSetGridPageActionGroup" stepKey="openAttributeSetPage"/> + <actionGroup ref="AdminOpenAttributeSetByNameActionGroup" stepKey="openDefaultAttributeSet"/> + <actionGroup ref="AssignAttributeToGroup" stepKey="assignAttributeToGroup"> + <argument name="group" value="Product Details"/> + <argument name="attribute" value="{{VisualSwatchProductAttribute.attribute_code}}"/> + </actionGroup> + <actionGroup ref="SaveAttributeSet" stepKey="saveAttributeSet"/> + + <!-- Create configurable product --> + <actionGroup ref="AdminOpenProductIndexPageActionGroup" stepKey="openProductIndexPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateConfigurableProduct"> + <argument name="product" value="ApiConfigurableProduct"/> + </actionGroup> + <!-- Fill all the necessary information such as weight, name, SKU etc --> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="ApiConfigurableProduct"/> + </actionGroup> + <!-- Click "Create Configurations" button, select created product attribute using the same Quantity for all products. Click "Generate products" button --> + <actionGroup ref="generateConfigurationsByAttributeCode" stepKey="addAttributeToProduct"> + <argument name="attributeCode" value="{{VisualSwatchProductAttribute.attribute_code}}"/> + </actionGroup> + <!-- Using this action to concatenate 2 strings to have unique identifier for grid --> + <executeJS function="return '{{VisualSwatchProductAttribute.attribute_code}}: red'" stepKey="attributeCodeRed"/> + <executeJS function="return '{{VisualSwatchProductAttribute.attribute_code}}: {{placeholderSmallImage.name}}'" stepKey="attributeCodeAdobeSmall"/> + <!-- Add images for the products --> + <attachFile selector="{{AdminDataGridTableSection.rowTemplate({$attributeCodeRed})}}{{AdminProductFormConfigurationsSection.fileUploaderInput}}" userInput="{{MagentoLogo.file}}" stepKey="uploadImageForFirstProduct"/> + <attachFile selector="{{AdminDataGridTableSection.rowTemplate({$attributeCodeAdobeSmall})}}{{AdminProductFormConfigurationsSection.fileUploaderInput}}" userInput="{{TestImageAdobe.file}}" stepKey="uploadImageForSecondProduct"/> + + <!-- Click "Save" button --> + <actionGroup ref="saveProductForm" stepKey="clickSaveButton"/> + + <!-- Delete all created product --> + <actionGroup ref="deleteProductBySku" stepKey="deleteCreatedProducts"> + <argument name="sku" value="{{ApiConfigurableProduct.sku}}"/> + </actionGroup> + </test> +</tests> From 0d69b039f5c3e4d2e4f06b79a5b76a2c35147689 Mon Sep 17 00:00:00 2001 From: DianaRusin <rusind95@gmail.com> Date: Mon, 16 Sep 2019 15:00:10 +0300 Subject: [PATCH 814/841] MC-228: Customer should be able to sort bundle products by price when viewing products list --- ...torefrontSortBundleProductsByPriceTest.xml | 32 ++++++++++--------- .../StorefrontCategoryActionGroup.xml | 6 ++++ ...rontCategoryPageSortProductActionGroup.xml | 19 ++++++++--- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml index 9a7a93d636ee0..18316e41241e4 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontSortBundleProductsByPriceTest.xml @@ -80,17 +80,17 @@ <createData entity="ApiBundleProductPriceViewRange" stepKey="createThirdBundleProduct"> <requiredEntity createDataKey="createCategory"/> </createData> - <createData entity="DropDownBundleOption" stepKey="thirdProductBundleOption"> + <createData entity="DropDownBundleOption" stepKey="createThirdProductBundleOption"> <requiredEntity createDataKey="createThirdBundleProduct"/> </createData> <createData entity="ApiBundleLink" stepKey="createBundleFirstLink"> <requiredEntity createDataKey="createThirdBundleProduct"/> - <requiredEntity createDataKey="thirdProductBundleOption"/> + <requiredEntity createDataKey="createThirdProductBundleOption"/> <requiredEntity createDataKey="createFirstProductForBundle"/> </createData> <createData entity="ApiBundleLink" stepKey="createBundleSecondLink"> <requiredEntity createDataKey="createThirdBundleProduct"/> - <requiredEntity createDataKey="thirdProductBundleOption"/> + <requiredEntity createDataKey="createThirdProductBundleOption"/> <requiredEntity createDataKey="createSecondProductForBundle"/> </createData> @@ -116,8 +116,10 @@ <argument name="categoryName" value="$$createCategory.name$$"/> </actionGroup> - <!-- Asset first bundle products in category product grid --> - <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createFirstBundleProduct.name$$)}}" stepKey="assertFirstBundleProduct"/> + <!-- Assert first bundle products in category product grid --> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertFirstBundleProduct"> + <argument name="product" value="$$createFirstBundleProduct$$"/> + </actionGroup> <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeFromForFirstBundleProduct"> <argument name="selector" value="{{StorefrontCategoryProductSection.priceFromByProductId($$createFirstBundleProduct.id$$)}}"/> <argument name="userInput" value="From $100.00"/> @@ -127,8 +129,10 @@ <argument name="userInput" value="To $123.00"/> </actionGroup> - <!-- Asset second bundle products in category product grid --> - <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSecondBundleProduct.name$$)}}" stepKey="assertSecondBundleProduct"/> + <!-- Assert second bundle products in category product grid --> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertSecondBundleProduct"> + <argument name="product" value="$$createSecondBundleProduct$$"/> + </actionGroup> <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeFromForSecondBundleProduct"> <argument name="selector" value="{{StorefrontCategoryProductSection.priceFromByProductId($$createSecondBundleProduct.id$$)}}"/> <argument name="userInput" value="From $10.00"/> @@ -138,8 +142,10 @@ <argument name="userInput" value="To $123.00"/> </actionGroup> - <!-- Asset third bundle products in category product grid --> - <seeElement selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createThirdBundleProduct.name$$)}}" stepKey="assertThirdBundleProduct"/> + <!-- Assert third bundle products in category product grid --> + <actionGroup ref="AssertProductOnCategoryPageActionGroup" stepKey="assertThirdBundleProduct"> + <argument name="product" value="$$createThirdBundleProduct$$"/> + </actionGroup> <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seePriceRangeFromForThirdBundleProduct"> <argument name="selector" value="{{StorefrontCategoryProductSection.priceFromByProductId($$createThirdBundleProduct.id$$)}}"/> <argument name="userInput" value="From $123.00"/> @@ -155,9 +161,7 @@ <!-- Sort products By Price --> <actionGroup ref="StorefrontCategoryPageSortProductActionGroup" stepKey="sortProductByPrice"/> <!-- Set Ascending Direction --> - <actionGroup ref="StorefrontCategoryPageSortDirectionActionGroup" stepKey="setAscendingDirection"> - <argument name="directionSelector" value="{{StorefrontCategoryTopToolbarSection.sortDirectionAsc}}"/> - </actionGroup> + <actionGroup ref="StorefrontCategoryPageSortAscendingActionGroup" stepKey="setAscendingDirection"/> <!-- Assert new products positions --> <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductFirstPosition"> @@ -174,9 +178,7 @@ </actionGroup> <!-- Set Descending Direction --> - <actionGroup ref="StorefrontCategoryPageSortDirectionActionGroup" stepKey="setDescendingDirection"> - <argument name="directionSelector" value="{{StorefrontCategoryTopToolbarSection.sortDirectionDesc}}"/> - </actionGroup> + <actionGroup ref="StorefrontCategoryPageSortDescendingActionGroup" stepKey="setDescendingDirection"/> <!-- Assert new products positions --> <actionGroup ref="AssertStorefrontElementVisibleActionGroup" stepKey="seeProductNewFirstPosition"> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml index 341a00d3158d6..7db22bfdcb719 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml @@ -78,6 +78,12 @@ <seeElement selector="{{StorefrontCategoryProductSection.ProductAddToCartByName(product.name)}}" stepKey="AssertAddToCart"/> </actionGroup> + <actionGroup name="AssertProductOnCategoryPageActionGroup" extends="StorefrontCheckCategorySimpleProduct"> + <remove keyForRemoval="AssertProductPrice"/> + <remove keyForRemoval="moveMouseOverProduct"/> + <remove keyForRemoval="AssertAddToCart"/> + </actionGroup> + <actionGroup name="StorefrontCheckAddToCartButtonAbsence"> <arguments> <argument name="product" defaultValue="_defaultProduct"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml index 751b3b0d4536d..64dd2c97a382f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryPageSortProductActionGroup.xml @@ -9,15 +9,24 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="StorefrontCategoryPageSortProductActionGroup"> + <annotations> + <description>Select "Sort by" parameter for sorting Products on Category page</description> + </annotations> <arguments> <argument name="sortBy" type="string" defaultValue="Price"/> </arguments> <selectOption selector="{{StorefrontCategoryTopToolbarSection.sortByDropdown}}" userInput="{{sortBy}}" stepKey="selectSortByParameter"/> </actionGroup> - <actionGroup name="StorefrontCategoryPageSortDirectionActionGroup"> - <arguments> - <argument name="directionSelector" type="string"/> - </arguments> - <click selector="{{directionSelector}}" stepKey="setDirection"/> + <actionGroup name="StorefrontCategoryPageSortAscendingActionGroup"> + <annotations> + <description>Set Ascending Direction for sorting Products on Category page</description> + </annotations> + <click selector="{{StorefrontCategoryTopToolbarSection.sortDirectionAsc}}" stepKey="setAscendingDirection"/> + </actionGroup> + <actionGroup name="StorefrontCategoryPageSortDescendingActionGroup"> + <annotations> + <description>Set Descending Direction for sorting Products on Category page</description> + </annotations> + <click selector="{{StorefrontCategoryTopToolbarSection.sortDirectionDesc}}" stepKey="setDescendingDirection"/> </actionGroup> </actionGroups> From 3f597f9e2ef1efc436a9376bd051c8918a93e97b Mon Sep 17 00:00:00 2001 From: Viktor Sevch <svitja@ukr.net> Date: Mon, 16 Sep 2019 15:46:00 +0300 Subject: [PATCH 815/841] MC-15845: Samples of Downloadable Products are not accessible, if product is disabled --- ...ifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml index fc8b28ac05530..f29bbcf925e26 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/VerifyDisableDownloadableProductSamplesAreNotAccessibleTest.xml @@ -90,7 +90,7 @@ <actionGroup ref="saveProductForm" stepKey="clickSaveProduct"/> <!-- Assert product is disable on Storefront --> - <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="openProductPage"> + <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="openCategoryPage"> <argument name="category" value="$createCategory$"/> </actionGroup> <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> From afaa6546785f3f2fe8a552a06e5c9bc32a01d276 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra <dmaraptor@gmail.com> Date: Mon, 16 Sep 2019 16:07:27 +0300 Subject: [PATCH 816/841] MC-11557: Check importing of configurable products with images present in filesystem --- .../Test/AdminExportImportConfigurableProductWithImagesTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml index 7c6c36b07ee2d..88f6e6c9f9039 100644 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml +++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml @@ -177,7 +177,7 @@ <actionGroup ref="AdminImportProductsActionGroup" stepKey="adminImportProduct"> <argument name="behavior" value="Add/Update"/> <argument name="importFile" value="export_import_configurable_product.csv"/> - <argument name="importMessage" value="Created: 1, Updated: 0, Deleted: 0"/> + <argument name="importNoticeMessage" value="Created: 1, Updated: 0, Deleted: 0"/> </actionGroup> <!-- Go to Catalog > Products: Configurable product exists --> From a33cd0c6b07de42c694b2489845df680c2538fb8 Mon Sep 17 00:00:00 2001 From: Stsiapan Korf <Stsiapan_Korf@epam.com> Date: Fri, 13 Sep 2019 15:10:17 +0300 Subject: [PATCH 817/841] MAGETWO-98748: Incorrect behavior in the category menu on the Storefront - PR stabilization --- app/code/Magento/Catalog/Plugin/Block/Topmenu.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php index d788525356fab..b4aa5bd960b01 100644 --- a/app/code/Magento/Catalog/Plugin/Block/Topmenu.php +++ b/app/code/Magento/Catalog/Plugin/Block/Topmenu.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Plugin\Block; use Magento\Catalog\Model\Category; @@ -156,12 +158,13 @@ private function getCurrentCategory() */ private function getCategoryAsArray($category, $currentCategory, $isParentActive) { + $categoryId = $category->getId(); return [ 'name' => $category->getName(), - 'id' => 'category-node-' . $category->getId(), + 'id' => 'category-node-' . $categoryId, 'url' => $this->catalogCategory->getCategoryUrl($category), - 'has_active' => in_array((string)$category->getId(), explode('/', $currentCategory->getPath()), true), - 'is_active' => $category->getId() == $currentCategory->getId(), + 'has_active' => in_array((string)$categoryId, explode('/', (string)$currentCategory->getPath()), true), + 'is_active' => $categoryId == $currentCategory->getId(), 'is_category' => true, 'is_parent_active' => $isParentActive ]; @@ -200,6 +203,7 @@ protected function getCategoryTree($storeId, $rootId) * @param \Magento\Theme\Block\Html\Topmenu $subject * @param string[] $result * @return string[] + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterGetCacheKeyInfo(\Magento\Theme\Block\Html\Topmenu $subject, array $result) { From 81c3ae3a075507c9e449f70d6d92adbfaca69fd3 Mon Sep 17 00:00:00 2001 From: Nikita Chubukov <nikita_chubukov@epam.com> Date: Mon, 16 Sep 2019 16:38:31 +0300 Subject: [PATCH 818/841] MC-15256: Exported customer without modification can not be imported - Fix static test --- 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 563bd2cd7f2b9..f86ebaea69730 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/Customer.php @@ -23,7 +23,7 @@ class Customer extends AbstractCustomer { /** - * Attribute collection name + * Collection name attribute */ const ATTRIBUTE_COLLECTION_NAME = \Magento\Customer\Model\ResourceModel\Attribute\Collection::class; From 2f6332ca6be7eeb9a7abae0ab7ffd83fff725ee7 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Mon, 16 Sep 2019 16:40:27 +0300 Subject: [PATCH 819/841] magento/magento2#24580: Added MFTF test. --- ...leProductWithSeparateLinksFromCartTest.xml | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml new file mode 100644 index 0000000000000..0b905964fd2d9 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/EditDownloadableProductWithSeparateLinksFromCartTest.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="EditDownloadableProductWithSeparateLinksFromCartTest"> + <annotations> + <features value="Catalog"/> + <stories value="Create Downloadable Product"/> + <title value="Edit downloadable product with separate links from cart test"/> + <description value="Product price should remain correct when editing downloadable product with separate links from cart."/> + <severity value="MAJOR"/> + <group value="Downloadable"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + + <!-- Create downloadable product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPageLoad"/> + <actionGroup ref="GoToSpecifiedCreateProductPage" stepKey="createProduct"> + <argument name="productType" value="downloadable"/> + </actionGroup> + + <!-- Fill downloadable product values --> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillDownloadableProductForm"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Add downloadable product to category --> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" + parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + + <!-- Fill downloadable link information before the creation link --> + <actionGroup ref="AdminAddDownloadableLinkInformationActionGroup" stepKey="addDownloadableLinkInformation"/> + + <!-- Links can be purchased separately --> + <checkOption selector="{{AdminProductDownloadableSection.isLinksPurchasedSeparately}}" + stepKey="checkOptionPurchaseSeparately"/> + + <!-- Add first downloadable link --> + <actionGroup ref="addDownloadableProductLinkWithMaxDownloads" stepKey="addFirstDownloadableProductLink"> + <argument name="link" value="downloadableLinkWithMaxDownloads"/> + </actionGroup> + + <!-- Add second downloadable link --> + <actionGroup ref="addDownloadableProductLink" stepKey="addSecondDownloadableProductLink"> + <argument name="link" value="downloadableLink"/> + </actionGroup> + + <!-- Save product --> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + </before> + <after> + <!-- Delete category --> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + + <!-- Delete created downloadable product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="DownloadableProduct"/> + </actionGroup> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Step 1: Navigate to store front Product page as guest --> + <amOnPage url="/{{DownloadableProduct.sku}}.html" + stepKey="amOnStorefrontProductPage"/> + + <!-- Step 2: Add checkbox for first link --> + <click + selector="{{StorefrontDownloadableProductSection.downloadableLinkByTitle(downloadableLinkWithMaxDownloads.title)}}" + stepKey="selectProductLink"/> + + <!-- Step 3: Add the Product to cart --> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="addProductToCart"> + <argument name="product" value="DownloadableProduct"/> + <argument name="productCount" value="1"/> + </actionGroup> + + <!-- Step 4: Open cart --> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="openShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <see selector="{{CheckoutCartProductSection.ProductPriceByName(DownloadableProduct.name)}}" userInput="$51.99" + stepKey="assertProductPriceInCart"/> + + <!-- Step 5: Edit Product in cart --> + <click selector="{{CheckoutCartProductSection.nthEditButton('1')}}" stepKey="clickEdit"/> + <waitForPageLoad stepKey="waitForEditPage"/> + + <!-- Step 6: Make sure Product price is correct --> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="51.99" stepKey="checkPrice"/> + </test> +</tests> From 938b31e74c64915503f5f8128166acf0e64ab8e9 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Mon, 16 Sep 2019 16:44:45 +0300 Subject: [PATCH 820/841] MC-13641: Saving configurable product with custom product attribute (images as swatches) --- ...veConfigurableProductWithAttributesImagesAndSwatchesTest.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml index 0b435250cd867..f94314fe94806 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSaveConfigurableProductWithAttributesImagesAndSwatchesTest.xml @@ -20,7 +20,7 @@ </annotations> <before> <!-- Login as admin --> - <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> <!-- Create a new product attribute --> <actionGroup ref="AdminOpenProductAttributePageActionGroup" stepKey="openProductAttributePage"/> From e39b03a4a597fdc6badbcde3ecaed022b713a67c Mon Sep 17 00:00:00 2001 From: Anton Siniorg <anton@speedupmate.com> Date: Mon, 16 Sep 2019 17:33:08 +0300 Subject: [PATCH 821/841] fix for the #24618 Magento_Braintree/js/view/payment/method-renderer/paypal.js accessing undefined variables --- .../view/frontend/web/js/view/payment/method-renderer/paypal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index ae9f69c405c2b..edb2064d87aa4 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -334,7 +334,7 @@ define([ } return { - line1: address.street[0], + line1: (_.isUndefined(address.street) || _.isUndefined(address.street[0]) ? '' : address.street[0], city: address.city, state: address.regionCode, postalCode: address.postcode, From e6dafe691cc88f2368853e7367eb1b4dcbf00384 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Mon, 16 Sep 2019 12:24:25 -0300 Subject: [PATCH 822/841] Editorial changes --- app/code/Magento/Backup/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backup/README.md b/app/code/Magento/Backup/README.md index fe5a34d84d0c0..e1167bc4f2429 100644 --- a/app/code/Magento/Backup/README.md +++ b/app/code/Magento/Backup/README.md @@ -1,8 +1,8 @@ # Magento_Backup module -The Magento_Backup allows administrators to perform backups and rollbacks. Types of backups include system, database and media backups. This module relies on the Cron module to schedule backups. +The Magento_Backup module allows administrators to perform backups and rollbacks. Types of backups include system, database and media backups. This module relies on the Cron module to schedule backups. -The Magento_Backup does not affect the storefront. +The Magento_Backup module does not affect the storefront. For more information about this module, see [Magento Backups](https://docs.magento.com/m2/ce/user_guide/system/backups.html) From 71fc4d76f181456765447c559610bfa97f454fc4 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Mon, 16 Sep 2019 12:07:53 -0500 Subject: [PATCH 823/841] MC-18996: GraphQl Url Resolver doesn't return any results if the url_key doesn't have the extension - add url prefix to schema --- .../Model/Resolver/CategoryUrlSuffix.php | 24 ++++++++++--------- .../Model/Resolver/ProductUrlSuffix.php | 6 +++-- .../CatalogUrlRewriteGraphQl/composer.json | 1 + .../etc/schema.graphqls | 4 ++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php index a68ab0916e42a..ba33f56f472d2 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php @@ -7,7 +7,7 @@ namespace Magento\CatalogUrlRewriteGraphQl\Model\Resolver; -use Magento\Framework\Exception\LocalizedException; +use Magento\Store\Api\Data\StoreInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; @@ -15,7 +15,7 @@ use Magento\Framework\App\Config\ScopeConfigInterface; /** - * Returns the url suffix for catalog + * Returns the url suffix for category */ class CategoryUrlSuffix implements ResolverInterface { @@ -26,7 +26,7 @@ class CategoryUrlSuffix implements ResolverInterface * * @var array */ - private $productUrlSuffix = []; + private $cateogryUrlSuffix = []; /** * @var ScopeConfigInterface @@ -35,11 +35,11 @@ class CategoryUrlSuffix implements ResolverInterface /** * @param ScopeConfigInterface $scopeConfig - * @param array $productUrlSuffix + * @param array $cateogryUrlSuffix */ - public function __construct(ScopeConfigInterface $scopeConfig, array $productUrlSuffix = []) + public function __construct(ScopeConfigInterface $scopeConfig, array $cateogryUrlSuffix = []) { - $this->productUrlSuffix = $productUrlSuffix; + $this->cateogryUrlSuffix = $cateogryUrlSuffix; $this->scopeConfig = $scopeConfig; } @@ -53,25 +53,27 @@ public function resolve( array $value = null, array $args = null ): string { - $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + $storeId = (int)$store->getId(); return $this->getProductUrlSuffix($storeId); } /** - * Retrieve product url suffix by store + * Retrieve category url suffix by store * * @param int $storeId * @return string */ private function getProductUrlSuffix(int $storeId): string { - if (!isset($this->productUrlSuffix[$storeId])) { - $this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue( + if (!isset($this->cateogryUrlSuffix[$storeId])) { + $this->cateogryUrlSuffix[$storeId] = $this->scopeConfig->getValue( self::XML_PATH_PRODUCT_URL_SUFFIX, ScopeInterface::SCOPE_STORE, $storeId ); } - return $this->productUrlSuffix[$storeId]; + return $this->cateogryUrlSuffix[$storeId]; } } diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php index 65d2b33f70b45..47ac15cc60754 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php @@ -7,7 +7,7 @@ namespace Magento\CatalogUrlRewriteGraphQl\Model\Resolver; -use Magento\Framework\Exception\LocalizedException; +use Magento\Store\Api\Data\StoreInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; @@ -53,7 +53,9 @@ public function resolve( array $value = null, array $args = null ): string { - $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); + /** @var StoreInterface $store */ + $store = $context->getExtensionAttributes()->getStore(); + $storeId = (int)$store->getId(); return $this->getProductUrlSuffix($storeId); } diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json index e276da0cc6fd8..202c573c2ae04 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/composer.json @@ -4,6 +4,7 @@ "type": "magento2-module", "require": { "php": "~7.1.3||~7.2.0||~7.3.0", + "magento/module-store": "*", "magento/module-catalog": "*", "magento/framework": "*" }, diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls index a5b9ee48bd208..3d58078425d01 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/etc/schema.graphqls @@ -3,13 +3,13 @@ interface ProductInterface { url_key: String @doc(description: "The part of the URL that identifies the product") - url_suffix: String @doc(description: "The part of the URL that is appended after the url key") @resolver(class: "Magento\\CatalogUrlRewriteGraphQl\\Model\\Resolver\\ProductUrlSuffix") + url_suffix: String @doc(description: "The part of the product URL that is appended after the url key") @resolver(class: "Magento\\CatalogUrlRewriteGraphQl\\Model\\Resolver\\ProductUrlSuffix") url_path: String @deprecated(reason: "Use product's `canonical_url` or url rewrites instead") url_rewrites: [UrlRewrite] @doc(description: "URL rewrites list") @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite") } interface CategoryInterface { - url_suffix: String @doc(description: "The part of the URL that is appended after the url key") @resolver(class: "Magento\\CatalogUrlRewriteGraphQl\\Model\\Resolver\\CategoryUrlSuffix") + url_suffix: String @doc(description: "The part of the category URL that is appended after the url key") @resolver(class: "Magento\\CatalogUrlRewriteGraphQl\\Model\\Resolver\\CategoryUrlSuffix") } input ProductFilterInput { From 57a2358c3de2d08c7e5afa94c18ac1d9c981b7c1 Mon Sep 17 00:00:00 2001 From: DianaRusin <rusind95@gmail.com> Date: Tue, 17 Sep 2019 08:38:59 +0300 Subject: [PATCH 824/841] MC-228: Customer should be able to sort bundle products by price when viewing products list --- .../Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml index 7db22bfdcb719..9393669f6e46d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml @@ -79,6 +79,9 @@ </actionGroup> <actionGroup name="AssertProductOnCategoryPageActionGroup" extends="StorefrontCheckCategorySimpleProduct"> + <annotations> + <description>EXTENDS:StorefrontCheckCategorySimpleProduct. Removes 'AssertProductPrice', 'moveMouseOverProduct', 'AssertAddToCart'</description> + </annotations> <remove keyForRemoval="AssertProductPrice"/> <remove keyForRemoval="moveMouseOverProduct"/> <remove keyForRemoval="AssertAddToCart"/> From 25b9454d1ac3d9635c0779249cf466f83e04e74a Mon Sep 17 00:00:00 2001 From: Hailong Zhao <hailongzh@hotmail.com> Date: Mon, 9 Sep 2019 10:50:12 -0400 Subject: [PATCH 825/841] Fix the comparison of directory path. --- .../Filesystem/Directory/PathValidator.php | 2 +- .../Test/Unit/Directory/PathValidatorTest.php | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/PathValidator.php b/lib/internal/Magento/Framework/Filesystem/Directory/PathValidator.php index fe0e6b37666b7..74e6cac7d77b3 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/PathValidator.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/PathValidator.php @@ -58,7 +58,7 @@ public function validate( } if (mb_strpos($actualPath, $realDirectoryPath) !== 0 - && $path .DIRECTORY_SEPARATOR !== $realDirectoryPath + && rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR !== $realDirectoryPath ) { throw new ValidatorException( new Phrase( diff --git a/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php b/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php new file mode 100644 index 0000000000000..1fe4596759ebd --- /dev/null +++ b/lib/internal/Magento/Framework/Filesystem/Test/Unit/Directory/PathValidatorTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Unit Test for \Magento\Framework\Filesystem\Directory\PathValidator + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Filesystem\Test\Unit\Directory; + +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\DriverInterface; + +class PathValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * \Magento\Framework\Filesystem\Driver + * + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $driver; + + /** + * @var \Magento\Framework\Filesystem\Directory\PathValidator + */ + protected $pathValidator; + + /** + * Set up + */ + protected function setUp() + { + $this->driver = $this->createMock(\Magento\Framework\Filesystem\Driver\File::class); + $this->pathValidator = new \Magento\Framework\Filesystem\Directory\PathValidator( + $this->driver + ); + } + + /** + * Tear down + */ + protected function tearDown() + { + $this->pathValidator = null; + } + + /** + * @param string $directoryPath + * @param string $path + * @param string $scheme + * @param bool $absolutePath + * @param string $prefix + * @dataProvider validateDataProvider + */ + public function testValidate($directoryPath, $path, $scheme, $absolutePath, $prefix) + { + $this->driver->expects($this->exactly(2)) + ->method('getRealPathSafety') + ->willReturnMap( + [ + [$directoryPath, $directoryPath], + [null, $prefix . $directoryPath . ltrim($path, '/')], + ] + ); + + $this->assertNull( + $this->pathValidator->validate($directoryPath, $path, $scheme, $absolutePath) + ); + } + + /** + * @return array + */ + public function validateDataProvider() + { + return [ + ['/directory/path/', '/directory/path/', '/', false, '/://'], + ['/directory/path/', '/var/.regenerate', null, false, ''], + ]; + } +} From 7e30a189028ecd77872989df5648ba033a55dd5f Mon Sep 17 00:00:00 2001 From: "al.kravchuk" <al.kravchuk@ism-ukraine.com> Date: Tue, 17 Sep 2019 09:27:52 +0300 Subject: [PATCH 826/841] magento/magento2#24626: Webapi Swager schema generation fail in case when Get endpoint has param with Extension Attributes [Case#2]. Fix warnings for empty objects in Get endpoints. --- app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php index 3ddb2e441ef91..f17e4792422cd 100644 --- a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php +++ b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php @@ -759,7 +759,8 @@ private function handleComplex($name, $type, $prefix, $isArray) $subPrefix ); } - return array_merge(...$queryNames); + + return empty($queryNames) ? [] : array_merge(...$queryNames); } /** From 202dfa06be16c6fc22a57e296260a5bcd316b4db Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Tue, 17 Sep 2019 09:44:35 +0300 Subject: [PATCH 827/841] MC-13641: Saving configurable product with custom product attribute (images as swatches) --- .../Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml index 6d877dac5cbf4..6ab1d71933826 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml @@ -51,7 +51,7 @@ <data key="value">5.00</data> </entity> <entity name="flatRateHandlingFeeDefault" type="handling_fee"> - <data key="value">F</data> + <data key="value">0</data> </entity> <entity name="flatRateSpecificerrmsgDefault" type="specificerrmsg"> <data key="value">This shipping method is not available. To use this shipping method, please contact us.</data> From fe069b20803d56a84411696d49edb97c42abcd69 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun <olga.vasyltsun@gmail.com> Date: Tue, 17 Sep 2019 12:32:44 +0300 Subject: [PATCH 828/841] MC-13641: Saving configurable product with custom product attribute (images as swatches) --- .../Catalog/Test/Mftf/Data/ProductData.xml | 5 -- ...hIncorrectNameThroughCustomOptionsTest.xml | 71 ------------------- ...ithout <script><:script> tags...\")'>.png" | 0 3 files changed, 76 deletions(-) delete mode 100644 app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml delete mode 100644 "dev/tests/acceptance/tests/_data/<img src=x onerror='alert(\"XSS without <script><:script> tags...\")'>.png" diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 1ea2af8927a04..e122615eb8aa4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -517,11 +517,6 @@ <requiredEntity type="product_option">ProductOptionArea</requiredEntity> <requiredEntity type="product_option">ProductOptionFile</requiredEntity> </entity> - <entity name="ProductFileOptionWithScriptTag" type="product"> - <var key="sku" entityType="product" entityKey="sku"/> - <data key="file"><img src=x onerror='alert("XSS without <script><:script> tags...")'>.png</data> - <requiredEntity type="product_option">ProductOptionFile</requiredEntity> - </entity> <entity name="ApiVirtualProductWithDescription" type="product"> <data key="sku" unique="suffix">api-virtual-product</data> <data key="type_id">virtual</data> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml deleted file mode 100644 index 2ea8e8820b1b4..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest.xml +++ /dev/null @@ -1,71 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontVerifyCannotLoadFileWithIncorrectNameThroughCustomOptionsTest"> - <annotations> - <features value="Catalog"/> - <stories value="Custom options"/> - <title value="Verify cannot load file with incorrect name through Custom options"/> - <description value="Verify cannot load file with incorrect name through Custom options"/> - <severity value="CRITICAL"/> - <testCaseId value="MC-13104"/> - <group value="catalog"/> - </annotations> - <before> - <!-- Create customer --> - <createData entity="Simple_US_Customer" stepKey="createCustomer"/> - <!-- Create category --> - <createData entity="_defaultCategory" stepKey="createCategory"/> - <!-- Create simple product --> - <createData entity="_defaultProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - <!-- Add file upload custom option to the product --> - <updateData createDataKey="createProduct" entity="ProductFileOptionWithScriptTag" stepKey="updateProductWithOption"/> - <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> - </before> - <after> - <!-- Delete product --> - <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> - <!-- Delete category --> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Delete customer --> - <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> - <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutCustomer"/> - </after> - - <!-- Login to storefront --> - <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> - <argument name="Customer" value="$$createCustomer$$"/> - </actionGroup> - - <!-- Open product page --> - <actionGroup ref="OpenStoreFrontProductPageActionGroup" stepKey="openProductPage"> - <argument name="productUrlKey" value="$$createProduct.custom_attributes[url_key]$$"/> - </actionGroup> - - <!-- Upload file --> - <actionGroup ref="StorefrontAttachOptionFileActionGroup" stepKey="selectAndAttachFile"> - <argument name="optionTitle" value="ProductOptionFile"/> - <argument name="file" value="ProductFileOptionWithScriptTag.file"/> - </actionGroup> - - <!-- Add product to cart --> - <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCartButton"/> - <waitForPageLoad stepKey="waitForProductAddToCart"/> - - <!-- Assert alert message --> - <waitForElementVisible selector="{{StorefrontProductPageSection.alertMessage}}" stepKey="waitForElementVisible"/> - <see selector="{{StorefrontProductPageSection.alertMessage}}" userInput="The file is empty. Select another file and try again." stepKey="seeAlertMessage"/> - - <!-- Assert cart is empty --> - <actionGroup ref="assertMiniCartEmpty" stepKey="assertMiniCartEmpty"/> - </test> -</tests> diff --git "a/dev/tests/acceptance/tests/_data/<img src=x onerror='alert(\"XSS without <script><:script> tags...\")'>.png" "b/dev/tests/acceptance/tests/_data/<img src=x onerror='alert(\"XSS without <script><:script> tags...\")'>.png" deleted file mode 100644 index e69de29bb2d1d..0000000000000 From d8534d2430f540373262dd3d47a8500f2c752d3e Mon Sep 17 00:00:00 2001 From: Anton Siniorg <anton@speedupmate.com> Date: Mon, 16 Sep 2019 17:33:08 +0300 Subject: [PATCH 829/841] fix for the #24618 Magento_Braintree/js/view/payment/method-renderer/paypal.js accessing undefined variables --- .../view/frontend/web/js/view/payment/method-renderer/paypal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index ae9f69c405c2b..e49ddbfb7a246 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -334,7 +334,7 @@ define([ } return { - line1: address.street[0], + line1: _.isUndefined(address.street) || _.isUndefined(address.street[0]) ? '' : address.street[0], city: address.city, state: address.regionCode, postalCode: address.postcode, From 9bbbf045dc2931a55952f3b9b356b1a2a72ef341 Mon Sep 17 00:00:00 2001 From: "al.kravchuk" <al.kravchuk@ism-ukraine.com> Date: Tue, 17 Sep 2019 14:02:55 +0300 Subject: [PATCH 830/841] magento/magento2#24626: Webapi Swager schema generation fail in case when Get endpoint has param with Extension Attributes [Case#2]. Add unit tests coverage. --- .../Unit/Model/Rest/Swagger/GeneratorTest.php | 131 +++++++++++++++--- 1 file changed, 114 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php b/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php index 172db875c6c49..67e361bb019d0 100644 --- a/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php +++ b/app/code/Magento/Webapi/Test/Unit/Model/Rest/Swagger/GeneratorTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Webapi\Test\Unit\Model\Rest\Swagger; /** @@ -137,11 +138,7 @@ public function testGenerate($serviceMetadata, $typeData, $schema) ->willReturn($serviceMetadata); $this->typeProcessorMock->expects($this->any()) ->method('getTypeData') - ->willReturnMap( - [ - ['TestModule5V2EntityAllSoapAndRest', $typeData], - ] - ); + ->willReturnMap($typeData); $this->typeProcessorMock->expects($this->any()) ->method('isTypeSimple') @@ -169,6 +166,96 @@ public function testGenerate($serviceMetadata, $typeData, $schema) public function generateDataProvider() { return [ + [ + [ + 'methods' => [ + 'execute' => [ + 'method' => 'execute', + 'inputRequired' => false, + 'isSecure' => false, + 'resources' => [ + "anonymous" + ], + 'methodAlias' => 'execute', + 'parameters' => [], + 'documentation' => 'Do Magic!', + 'interface' => [ + 'in' => [ + 'parameters' => [ + 'searchRequest' => [ + 'type' => 'DreamVendorDreamModuleApiDataSearchRequestInterface', + 'required' => true, + 'documentation' => "" + ] + ] + ], + 'out' => [ + 'parameters' => [ + 'result' => [ + 'type' => 'DreamVendorDreamModuleApiDataSearchResultInterface', + 'documentation' => null, + 'required' => true + ] + ] + ] + ] + ] + ], + 'class' => 'DreamVendor\DreamModule\Api\ExecuteStuff', + 'description' => '', + 'routes' => [ + '/V1/dream-vendor/dream-module/execute-stuff' => [ + 'GET' => [ + 'method' => 'execute', + 'parameters' => [] + ] + ] + ] + ], + [ + [ + 'DreamVendorDreamModuleApiDataSearchRequestInterface', + [ + 'documentation' => '', + 'parameters' => [ + 'stuff' => [ + 'type' => 'DreamVendorDreamModuleApiDataStuffInterface', + 'required' => true, + 'documentation' => 'Empty Extension Point' + ] + ] + ] + ], + [ + 'DreamVendorDreamModuleApiDataSearchResultInterface', + [ + 'documentation' => '', + 'parameters' => [ + 'totalCount' => [ + 'type' => 'int', + 'required' => true, + 'documentation' => 'Processed count.' + ], + 'stuff' => [ + 'type' => 'DreamVendorDreamModuleApiDataStuffInterface', + 'required' => true, + 'documentation' => 'Empty Extension Point' + ] + ] + ] + ], + [ + 'DreamVendorDreamModuleApiDataStuffInterface', + [ + 'documentation' => '', + 'parameters' => [] + ] + ] + ], + // @codingStandardsIgnoreStart + '{"swagger":"2.0","info":{"version":"","title":""},"host":"magento.host","basePath":"/rest/default","schemes":["http://"],"tags":[{"name":"testModule5AllSoapAndRestV2","description":""}],"paths":{"/V1/dream-vendor/dream-module/execute-stuff":{"get":{"tags":["testModule5AllSoapAndRestV2"],"description":"Do Magic!","operationId":"operationNameGet","consumes":["application/json","application/xml"],"produces":["application/json","application/xml"],"responses":{"200":{"description":"200 Success.","schema":{"$ref":"#/definitions/dream-vendor-dream-module-api-data-search-result-interface"}},"default":{"description":"Unexpected error","schema":{"$ref":"#/definitions/error-response"}}}}}},"definitions":{"error-response":{"type":"object","properties":{"message":{"type":"string","description":"Error message"},"errors":{"$ref":"#/definitions/error-errors"},"code":{"type":"integer","description":"Error code"},"parameters":{"$ref":"#/definitions/error-parameters"},"trace":{"type":"string","description":"Stack trace"}},"required":["message"]},"error-errors":{"type":"array","description":"Errors list","items":{"$ref":"#/definitions/error-errors-item"}},"error-errors-item":{"type":"object","description":"Error details","properties":{"message":{"type":"string","description":"Error message"},"parameters":{"$ref":"#/definitions/error-parameters"}}},"error-parameters":{"type":"array","description":"Error parameters list","items":{"$ref":"#/definitions/error-parameters-item"}},"error-parameters-item":{"type":"object","description":"Error parameters item","properties":{"resources":{"type":"string","description":"ACL resource"},"fieldName":{"type":"string","description":"Missing or invalid field name"},"fieldValue":{"type":"string","description":"Incorrect field value"}}},"dream-vendor-dream-module-api-data-search-result-interface":{"type":"object","description":"","properties":{"total_count":{"type":"integer","description":"Processed count."},"stuff":{"$ref":"#/definitions/dream-vendor-dream-module-api-data-stuff-interface"}},"required":["total_count","stuff"]},"dream-vendor-dream-module-api-data-stuff-interface":{"type":"object","description":""}}}' + // @codingStandardsIgnoreEnd + ], [ [ 'methods' => [ @@ -213,12 +300,17 @@ public function generateDataProvider() ], ], [ - 'documentation' => 'Some Data Object', - 'parameters' => [ - 'price' => [ - 'type' => 'int', - 'required' => true, - 'documentation' => "" + [ + 'TestModule5V2EntityAllSoapAndRest', + [ + 'documentation' => 'Some Data Object', + 'parameters' => [ + 'price' => [ + 'type' => 'int', + 'required' => true, + 'documentation' => "" + ] + ] ] ] ], @@ -261,12 +353,17 @@ public function generateDataProvider() ], ], [ - 'documentation' => 'Some Data Object', - 'parameters' => [ - 'price' => [ - 'type' => 'int', - 'required' => true, - 'documentation' => "" + [ + 'TestModule5V2EntityAllSoapAndRest', + [ + 'documentation' => 'Some Data Object', + 'parameters' => [ + 'price' => [ + 'type' => 'int', + 'required' => true, + 'documentation' => "" + ] + ] ] ] ], From 8dd87681d490a5958d1ea1435df9d6a2adc98ce6 Mon Sep 17 00:00:00 2001 From: "al.kravchuk" <al.kravchuk@ism-ukraine.com> Date: Tue, 17 Sep 2019 14:03:17 +0300 Subject: [PATCH 831/841] magento/magento2#24626: Webapi Swager schema generation fail in case when Get endpoint has param with Extension Attributes [Case#2]. Try to fix static tests. --- app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php index f17e4792422cd..ebc716b3e2f46 100644 --- a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php +++ b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php @@ -38,7 +38,9 @@ class Generator extends AbstractSchemaGenerator */ const UNAUTHORIZED_DESCRIPTION = '401 Unauthorized'; - /** Array signifier */ + /** + * Array signifier + */ const ARRAY_SIGNIFIER = '[0]'; /** From f5e8efa71e4756392de160ab1cbd99eb7fef1210 Mon Sep 17 00:00:00 2001 From: Lusine Papyan <Lusine_Papyan@epam.com> Date: Tue, 17 Sep 2019 15:31:45 +0400 Subject: [PATCH 832/841] MAGETWO-70803: [GITHUB] Inconsistent CSV file Import error: #7495 - Updated automated test script --- .../AdminImportProductsActionGroup.xml | 19 +++++++++++++------ ...ImportCSVFileCorrectDifferentFilesTest.xml | 10 ++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml index 0ba1cd520b48a..9063916e9f502 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml @@ -46,11 +46,18 @@ <see selector="{{AdminImportValidationMessagesSection.notice}}" userInput="{{validationNoticeMessage}}" after="waitForValidationNoticeMessage" stepKey="seeValidationNoticeMessage"/> <see selector="{{AdminImportValidationMessagesSection.success}}" userInput="{{validationMessage}}" after="seeValidationNoticeMessage" stepKey="seeValidationMessage"/> </actionGroup> - <actionGroup name="checkDataForImportProductActionGroup" extends="AdminImportProductsActionGroup"> - <remove keyForRemoval="clickImportButton"/> - <remove keyForRemoval="AdminImportMainSectionLoad2"/> - <remove keyForRemoval="assertSuccessMessage"/> - <remove keyForRemoval="AdminMessagesSection"/> - <remove keyForRemoval="seeImportMessage"/> + <actionGroup name="AdminCheckDataForImportProductActionGroup"> + <arguments> + <argument name="behavior" type="string" defaultValue="Add/Update"/> + <argument name="importFile" type="string"/> + </arguments> + <amOnPage url="{{AdminImportIndexPage.url}}" stepKey="goToImportIndexPage"/> + <waitForPageLoad stepKey="adminImportMainSectionLoad"/> + <selectOption selector="{{AdminImportMainSection.entityType}}" userInput="Products" stepKey="selectProductsOption"/> + <waitForElementVisible selector="{{AdminImportMainSection.importBehavior}}" stepKey="waitForImportBehaviorElementVisible"/> + <selectOption selector="{{AdminImportMainSection.importBehavior}}" userInput="{{behavior}}" stepKey="selectImportBehaviorOption"/> + <attachFile selector="{{AdminImportMainSection.selectFileToImport}}" userInput="{{importFile}}" stepKey="attachFileForImport"/> + <click selector="{{AdminImportHeaderSection.checkDataButton}}" stepKey="clickCheckDataButton"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml index 349d3415af06e..eb84929ec8d93 100644 --- a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminProductImportCSVFileCorrectDifferentFilesTest.xml @@ -20,27 +20,21 @@ </annotations> <before> <!--Login as Admin--> - <comment userInput="Login as Admin" stepKey="commentLogin"/> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> </before> <after> <!--Logout from Admin--> - <comment userInput="Logout from Admin" stepKey="commentLogout"/> <actionGroup ref="logout" stepKey="logoutFromAdmin"/> </after> <!--Check data products with add/update behavior--> - <comment userInput="Check data products with add/update behavior" stepKey="commentCheckData"/> - <actionGroup ref="checkDataForImportProductActionGroup" stepKey="adminImportProducts"> + <actionGroup ref="AdminCheckDataForImportProductActionGroup" stepKey="adminImportProducts"> <argument name="behavior" value="Add/Update"/> <argument name="importFile" value="BB-ProductsWorking.csv"/> - <argument name="importMessage" value="Checked rows: 28, checked entities: 28, invalid rows: 0, total errors: 0"/> </actionGroup> <see selector="{{AdminImportMainSection.messageSuccess}}" userInput='File is valid! To start import process press "Import" button' stepKey="seeSuccessMessage"/> - <actionGroup ref="checkDataForImportProductActionGroup" stepKey="adminImportProducts1"> + <actionGroup ref="AdminCheckDataForImportProductActionGroup" stepKey="adminImportProducts1"> <argument name="behavior" value="Add/Update"/> <argument name="importFile" value="BB-Products.csv"/> - <argument name="importMessage" value="Checked rows: 117, checked entities: 115, invalid rows: 2, total errors: 2"/> - <argument name="checkMessage" value='Curly quotes used instead of straight quotes in row(s): 84, 85'/> </actionGroup> <see selector="{{AdminImportMainSection.messageError}}" userInput='Curly quotes used instead of straight quotes in row(s): 84, 85' stepKey="seeErrorMessage"/> </test> From f1eb7a33bb28ff046c0ae3422084376331cb22d0 Mon Sep 17 00:00:00 2001 From: "al.kravchuk" <al.kravchuk@ism-ukraine.com> Date: Tue, 17 Sep 2019 14:38:17 +0300 Subject: [PATCH 833/841] magento/magento2#24626: Webapi Swager schema generation fail in case when Get endpoint has param with Extension Attributes [Case#2]. Try to fix static tests. --- app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php index ebc716b3e2f46..f38c0f0978536 100644 --- a/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php +++ b/app/code/Magento/Webapi/Model/Rest/Swagger/Generator.php @@ -33,14 +33,10 @@ class Generator extends AbstractSchemaGenerator */ const ERROR_SCHEMA = '#/definitions/error-response'; - /** - * Unauthorized description - */ + /** Unauthorized description */ const UNAUTHORIZED_DESCRIPTION = '401 Unauthorized'; - /** - * Array signifier - */ + /** Array signifier */ const ARRAY_SIGNIFIER = '[0]'; /** From b2e78e3a65d568e48c12bc989685e3066985f826 Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <p.bystritsky@yandex.ru> Date: Tue, 17 Sep 2019 17:23:10 +0300 Subject: [PATCH 834/841] magento/magento2#24089: MFTF test added. --- .../Test/Mftf/Page/AdminEditCustomerPage.xml | 2 + .../Mftf/Section/AdminCustomerCartSection.xml | 14 ++++ .../AdminCustomerInformationSection.xml | 22 ++++++ ...ectNavigateFromCustomerViewCartProduct.xml | 74 +++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCartSection.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml create mode 100644 app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml index 9bd382da8eb92..79fb18afaad53 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml @@ -12,6 +12,8 @@ <section name="AdminCustomerAddressesGridSection"/> <section name="AdminCustomerAddressesGridActionsSection"/> <section name="AdminCustomerAddressesSection"/> + <section name="AdminCustomerCartSection" /> + <section name="AdminCustomerInformationSection" /> <section name="AdminCustomerMainActionsSection"/> <section name="AdminEditCustomerAddressesSection" /> </page> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCartSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCartSection.xml new file mode 100644 index 0000000000000..5c8b8907db43a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerCartSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerCartSection"> + <element name="cartItem" type="button" selector="#customer_cart_grid_table tbody tr:nth-of-type({{row}}) .col-product_id" parameterized="true" timeout="5"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml new file mode 100644 index 0000000000000..d680015230b9d --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerInformationSection.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerInformationSection"> + <element name="customerView" type="button" selector="#tab_customer_edit_tab_view_content"/> + <element name="accountInformation" type="button" selector="#tab_customer"/> + <element name="addresses" type="button" selector="#tab_address"/> + <element name="orders" type="button" selector="#tab_orders_content"/> + <element name="shoppingCart" type="button" selector="#tab_cart_content"/> + <element name="newsletter" type="button" selector="#tab_newsletter_content"/> + <element name="billingAgreements" type="button" selector="#tab_customer_edit_tab_agreements_content"/> + <element name="productReviews" type="button" selector="#tab_reviews_content"/> + <element name="wishList" type="button" selector="#tab_wishlist_content"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml new file mode 100644 index 0000000000000..9de2339f2e217 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminProductBackRedirectNavigateFromCustomerViewCartProduct.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminProductBackRedirectNavigateFromCustomerViewCartProduct"> + <annotations> + <features value="Customer"/> + <title value="Product back redirect navigate from customer view cart product"/> + <description value="Back button on product page is redirecting to customer page if opened form shopping cart"/> + <severity value="MINOR"/> + <group value="Customer"/> + </annotations> + <before> + <!-- Create new product--> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create new customer--> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + + <!-- Login as admin--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <!-- Go to storefront as customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="customerLogin"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Add product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!-- Navigate to customer edit page in admin --> + <amOnPage url="{{AdminCustomerPage.url}}edit/id/$$createCustomer.id$$/" stepKey="openCustomerEditPage"/> + <waitForPageLoad stepKey="waitForCustomerEditPage"/> + + <!-- Open shopping cart --> + <click selector="{{AdminCustomerInformationSection.shoppingCart}}" stepKey="clickShoppingCartButton"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> + + <!-- Open product --> + <click selector="{{AdminCustomerCartSection.cartItem('1')}}" stepKey="openProduct"/> + + <!-- Go back to customer page --> + <click selector="{{AdminProductFormActionSection.backButton}}" stepKey="goBackToCustomerPage"/> + + <!-- Check current page is customer page --> + <seeInCurrentUrl stepKey="onCustomerAccountPage" url="{{AdminCustomerPage.url}}edit/id/$$createCustomer.id$$/"/> + + <after> + <!--Delete product--> + <deleteData stepKey="deleteProduct" createDataKey="createProduct"/> + + <!--Delete category--> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + + <!--Delete customer--> + <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> + + <!-- Sign out--> + <actionGroup ref="SignOut" stepKey="signOut"/> + </after> + </test> +</tests> From 02989b05b9ddbb5f2cfa199e59adc26ffe55b76d Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Tue, 17 Sep 2019 12:57:05 -0500 Subject: [PATCH 835/841] MC-18996: GraphQl Url Resolver doesn't return any results if the url_key doesn't have the extension - add tests --- .../UrlRewriteGraphQl/etc/schema.graphqls | 2 +- .../CatalogUrlRewrite/UrlResolverTest.php | 391 ++++++++++++++++++ .../GraphQl/CmsUrlRewrite/UrlResolverTest.php | 101 +++++ .../GraphQl/UrlRewrite/UrlResolverTest.php | 374 +---------------- 4 files changed, 494 insertions(+), 374 deletions(-) create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php create mode 100644 dev/tests/api-functional/testsuite/Magento/GraphQl/CmsUrlRewrite/UrlResolverTest.php diff --git a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls index 92d237d3f01e1..e7291aae4b8e2 100644 --- a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page") @cache(cacheIdentity: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite\\UrlResolverIdentity") + urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page, using as input a url_key prepended by the url_suffix, if one exists") @cache(cacheIdentity: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite\\UrlResolverIdentity") } type EntityUrl @doc(description: "EntityUrl is an output object containing the `id`, `relative_url`, and `type` attributes") { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php new file mode 100644 index 0000000000000..1b2776e66b7e7 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php @@ -0,0 +1,391 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogGraphQl\UrlRewrite; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\UrlRewrite\Model\UrlFinderInterface; +use Magento\UrlRewrite\Model\UrlRewrite; + +/** + * Test the GraphQL endpoint's URLResolver query to verify canonical URL's are correctly returned. + */ +class UrlResolverTest extends GraphQlAbstract +{ + /** @var ObjectManager */ + private $objectManager; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * Tests if target_path(relative_url) is resolved for Product entity + * + * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php + */ + public function testProductUrlResolver() + { + $productSku = 'p002'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + $storeId = $product->getStoreId(); + + $query + = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + url_key + url_suffix + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $urlPath = $response['products']['items'][0]['url_key'] . $response['products']['items'][0]['url_suffix']; + + /** @var UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(UrlFinderInterface::class); + $actualUrls = $urlFinder->findOneByData( + [ + 'request_path' => $urlPath, + 'store_id' => $storeId + ] + ); + $targetPath = $actualUrls->getTargetPath(); + $expectedType = $actualUrls->getEntityType(); + + $query + = <<<QUERY +{ + urlResolver(url:"{$urlPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); + } + + /** + * Tests the use case where relative_url is provided as resolver input in the Query + * + * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php + */ + public function testProductUrlWithCanonicalUrlInput() + { + $productSku = 'p002'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + $storeId = $product->getStoreId(); + + $query + = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + url_key + url_suffix + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $urlPath = $response['products']['items'][0]['url_key'] . $response['products']['items'][0]['url_suffix']; + + /** @var UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(UrlFinderInterface::class); + $actualUrls = $urlFinder->findOneByData( + [ + 'request_path' => $urlPath, + 'store_id' => $storeId + ] + ); + $targetPath = $actualUrls->getTargetPath(); + $expectedType = $actualUrls->getEntityType(); + $canonicalPath = $actualUrls->getTargetPath(); + $query + = <<<QUERY +{ + urlResolver(url:"{$canonicalPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); + } + + /** + * Test for category entity + * + * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php + */ + public function testCategoryUrlResolver() + { + $productSku = 'p002'; + $categoryUrlPath = 'cat-1.html'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + $storeId = $product->getStoreId(); + + /** @var UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(UrlFinderInterface::class); + $actualUrls = $urlFinder->findOneByData( + [ + 'request_path' => $categoryUrlPath, + 'store_id' => $storeId + ] + ); + $categoryId = $actualUrls->getEntityId(); + $targetPath = $actualUrls->getTargetPath(); + $expectedType = $actualUrls->getEntityType(); + + $query + = <<<QUERY +{ + category(id:{$categoryId}) { + url_key + url_suffix + } +} +QUERY; + $response = $this->graphQlQuery($query); + $urlPath = $response['category']['url_key'] . $response['category']['url_suffix']; + + $query + = <<<QUERY +{ + urlResolver(url:"{$urlPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertEquals($categoryId, $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); + } + + /** + * Tests the use case where the url_key of the existing product is changed + * + * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php + */ + public function testProductUrlRewriteResolver() + { + $productSku = 'p002'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + $storeId = $product->getStoreId(); + $product->setUrlKey('p002-new')->save(); + + $query + = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) + { + items { + url_key + url_suffix + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + $urlPath = $response['products']['items'][0]['url_key'] . $response['products']['items'][0]['url_suffix']; + + $this->assertEquals($urlPath, 'p002-new' . $response['products']['items'][0]['url_suffix']); + + /** @var UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(UrlFinderInterface::class); + $actualUrls = $urlFinder->findOneByData( + [ + 'request_path' => $urlPath, + 'store_id' => $storeId + ] + ); + $targetPath = $actualUrls->getTargetPath(); + $expectedType = $actualUrls->getEntityType(); + $query + = <<<QUERY +{ + urlResolver(url:"{$urlPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); + } + + /** + * Tests if null is returned when an invalid request_path is provided as input to urlResolver + * + * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php + */ + public function testInvalidUrlResolverInput() + { + $productSku = 'p002'; + $urlPath = 'p002'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + $storeId = $product->getStoreId(); + + /** @var UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(UrlFinderInterface::class); + $urlFinder->findOneByData( + [ + 'request_path' => $urlPath, + 'store_id' => $storeId + ] + ); + $query + = <<<QUERY +{ + urlResolver(url:"{$urlPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertNull($response['urlResolver']); + } + + /** + * Test for category entity with leading slash + * + * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php + */ + public function testCategoryUrlWithLeadingSlash() + { + $productSku = 'p002'; + $categoryUrlPath = 'cat-1.html'; + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get($productSku, false, null, true); + $storeId = $product->getStoreId(); + + /** @var UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(UrlFinderInterface::class); + $actualUrls = $urlFinder->findOneByData( + [ + 'request_path' => $categoryUrlPath, + 'store_id' => $storeId + ] + ); + $categoryId = $actualUrls->getEntityId(); + $targetPath = $actualUrls->getTargetPath(); + $expectedType = $actualUrls->getEntityType(); + + $query + = <<<QUERY +{ + category(id:{$categoryId}) { + url_key + url_suffix + } +} +QUERY; + $response = $this->graphQlQuery($query); + $urlPath = $response['category']['url_key'] . $response['category']['url_suffix']; + + $query = <<<QUERY +{ + urlResolver(url:"/{$urlPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertEquals($categoryId, $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); + } + + /** + * Test for custom type which point to the valid product/category/cms page. + * + * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php + */ + public function testGetNonExistentUrlRewrite() + { + $urlPath = 'non-exist-product.html'; + /** @var UrlRewrite $urlRewrite */ + $urlRewrite = $this->objectManager->create(UrlRewrite::class); + $urlRewrite->load($urlPath, 'request_path'); + + /** @var UrlFinderInterface $urlFinder */ + $urlFinder = $this->objectManager->get(UrlFinderInterface::class); + $actualUrls = $urlFinder->findOneByData( + [ + 'request_path' => $urlPath, + 'store_id' => 1 + ] + ); + $targetPath = $actualUrls->getTargetPath(); + + $query = <<<QUERY +{ + urlResolver(url:"{$urlPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertEquals('PRODUCT', $response['urlResolver']['type']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CmsUrlRewrite/UrlResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CmsUrlRewrite/UrlResolverTest.php new file mode 100644 index 0000000000000..ece421925a31c --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CmsUrlRewrite/UrlResolverTest.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\CmsUrlRewrite; + +use Magento\CmsUrlRewrite\Model\CmsPageUrlRewriteGenerator; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Cms\Helper\Page as PageHelper; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * Test the GraphQL endpoint's URLResolver query to verify canonical URL's are correctly returned. + */ +class UrlResolverTest extends GraphQlAbstract +{ + /** @var ObjectManager */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * @magentoApiDataFixture Magento/Cms/_files/pages.php + */ + public function testCMSPageUrlResolver() + { + /** @var \Magento\Cms\Model\Page $page */ + $page = $this->objectManager->get(\Magento\Cms\Model\Page::class); + $page->load('page100'); + $cmsPageId = $page->getId(); + $requestPath = $page->getIdentifier(); + + /** @var \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $urlPathGenerator */ + $urlPathGenerator = $this->objectManager->get(\Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator::class); + + /** @param \Magento\Cms\Api\Data\PageInterface $page */ + $targetPath = $urlPathGenerator->getCanonicalUrlPath($page); + $expectedEntityType = CmsPageUrlRewriteGenerator::ENTITY_TYPE; + + $query + = <<<QUERY +{ + urlResolver(url:"{$requestPath}") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertEquals($cmsPageId, $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals(strtoupper(str_replace('-', '_', $expectedEntityType)), $response['urlResolver']['type']); + } + + /** + * Test resolution of '/' path to home page + */ + public function testResolveSlash() + { + /** @var \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfigInterface */ + $scopeConfigInterface = $this->objectManager->get(ScopeConfigInterface::class); + $homePageIdentifier = $scopeConfigInterface->getValue( + PageHelper::XML_PATH_HOME_PAGE, + ScopeInterface::SCOPE_STORE + ); + /** @var \Magento\Cms\Model\Page $page */ + $page = $this->objectManager->get(\Magento\Cms\Model\Page::class); + $page->load($homePageIdentifier); + $homePageId = $page->getId(); + /** @var \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $urlPathGenerator */ + $urlPathGenerator = $this->objectManager->get(\Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator::class); + /** @param \Magento\Cms\Api\Data\PageInterface $page */ + $targetPath = $urlPathGenerator->getCanonicalUrlPath($page); + $query + = <<<QUERY +{ + urlResolver(url:"/") + { + id + relative_url + type + } +} +QUERY; + $response = $this->graphQlQuery($query); + $this->assertArrayHasKey('urlResolver', $response); + $this->assertEquals($homePageId, $response['urlResolver']['id']); + $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); + $this->assertEquals('CMS_PAGE', $response['urlResolver']['type']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php index 8eaf33483531d..bd20741fb7417 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php @@ -7,23 +7,15 @@ namespace Magento\GraphQl\UrlRewrite; -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\CmsUrlRewrite\Model\CmsPageUrlRewriteGenerator; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\GraphQlAbstract; -use Magento\UrlRewrite\Model\UrlFinderInterface; -use Magento\Cms\Helper\Page as PageHelper; -use Magento\Store\Model\ScopeInterface; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\UrlRewrite\Model\UrlRewrite; /** * Test the GraphQL endpoint's URLResolver query to verify canonical URL's are correctly returned. */ class UrlResolverTest extends GraphQlAbstract { - - /** @var ObjectManager */ + /** @var ObjectManager */ private $objectManager; protected function setUp() @@ -31,370 +23,6 @@ protected function setUp() $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); } - /** - * Tests if target_path(relative_url) is resolved for Product entity - * - * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php - */ - public function testProductUrlResolver() - { - $productSku = 'p002'; - $urlPath = 'p002.html'; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku, false, null, true); - $storeId = $product->getStoreId(); - - /** @var UrlFinderInterface $urlFinder */ - $urlFinder = $this->objectManager->get(UrlFinderInterface::class); - $actualUrls = $urlFinder->findOneByData( - [ - 'request_path' => $urlPath, - 'store_id' => $storeId - ] - ); - $targetPath = $actualUrls->getTargetPath(); - $expectedType = $actualUrls->getEntityType(); - $query - = <<<QUERY -{ - urlResolver(url:"{$urlPath}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); - } - - /** - * Tests the use case where relative_url is provided as resolver input in the Query - * - * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php - */ - public function testProductUrlWithCanonicalUrlInput() - { - $productSku = 'p002'; - $urlPath = 'p002.html'; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku, false, null, true); - $storeId = $product->getStoreId(); - $product->getUrlKey(); - - /** @var UrlFinderInterface $urlFinder */ - $urlFinder = $this->objectManager->get(UrlFinderInterface::class); - $actualUrls = $urlFinder->findOneByData( - [ - 'request_path' => $urlPath, - 'store_id' => $storeId - ] - ); - $targetPath = $actualUrls->getTargetPath(); - $expectedType = $actualUrls->getEntityType(); - $canonicalPath = $actualUrls->getTargetPath(); - $query - = <<<QUERY -{ - urlResolver(url:"{$canonicalPath}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); - } - - /** - * Test for category entity - * - * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php - */ - public function testCategoryUrlResolver() - { - $productSku = 'p002'; - $urlPath2 = 'cat-1.html'; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku, false, null, true); - $storeId = $product->getStoreId(); - - /** @var UrlFinderInterface $urlFinder */ - $urlFinder = $this->objectManager->get(UrlFinderInterface::class); - $actualUrls = $urlFinder->findOneByData( - [ - 'request_path' => $urlPath2, - 'store_id' => $storeId - ] - ); - $categoryId = $actualUrls->getEntityId(); - $targetPath = $actualUrls->getTargetPath(); - $expectedType = $actualUrls->getEntityType(); - $query - = <<<QUERY -{ - urlResolver(url:"{$urlPath2}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals($categoryId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); - } - - /** - * @magentoApiDataFixture Magento/Cms/_files/pages.php - */ - public function testCMSPageUrlResolver() - { - /** @var \Magento\Cms\Model\Page $page */ - $page = $this->objectManager->get(\Magento\Cms\Model\Page::class); - $page->load('page100'); - $cmsPageId = $page->getId(); - $requestPath = $page->getIdentifier(); - - /** @var \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $urlPathGenerator */ - $urlPathGenerator = $this->objectManager->get(\Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator::class); - - /** @param \Magento\Cms\Api\Data\PageInterface $page */ - $targetPath = $urlPathGenerator->getCanonicalUrlPath($page); - $expectedEntityType = CmsPageUrlRewriteGenerator::ENTITY_TYPE; - - $query - = <<<QUERY -{ - urlResolver(url:"{$requestPath}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertEquals($cmsPageId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - $this->assertEquals(strtoupper(str_replace('-', '_', $expectedEntityType)), $response['urlResolver']['type']); - } - - /** - * Tests the use case where the url_key of the existing product is changed - * - * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php - */ - public function testProductUrlRewriteResolver() - { - $productSku = 'p002'; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku, false, null, true); - $storeId = $product->getStoreId(); - $product->setUrlKey('p002-new')->save(); - $urlPath = $product->getUrlKey() . '.html'; - $this->assertEquals($urlPath, 'p002-new.html'); - - /** @var UrlFinderInterface $urlFinder */ - $urlFinder = $this->objectManager->get(UrlFinderInterface::class); - $actualUrls = $urlFinder->findOneByData( - [ - 'request_path' => $urlPath, - 'store_id' => $storeId - ] - ); - $targetPath = $actualUrls->getTargetPath(); - $expectedType = $actualUrls->getEntityType(); - $query - = <<<QUERY -{ - urlResolver(url:"{$urlPath}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals($product->getEntityId(), $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); - } - - /** - * Tests if null is returned when an invalid request_path is provided as input to urlResolver - * - * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php - */ - public function testInvalidUrlResolverInput() - { - $productSku = 'p002'; - $urlPath = 'p002'; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku, false, null, true); - $storeId = $product->getStoreId(); - - /** @var UrlFinderInterface $urlFinder */ - $urlFinder = $this->objectManager->get(UrlFinderInterface::class); - $urlFinder->findOneByData( - [ - 'request_path' => $urlPath, - 'store_id' => $storeId - ] - ); - $query - = <<<QUERY -{ - urlResolver(url:"{$urlPath}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertNull($response['urlResolver']); - } - - /** - * Test for category entity with leading slash - * - * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php - */ - public function testCategoryUrlWithLeadingSlash() - { - $productSku = 'p002'; - $urlPath = 'cat-1.html'; - /** @var ProductRepositoryInterface $productRepository */ - $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); - $product = $productRepository->get($productSku, false, null, true); - $storeId = $product->getStoreId(); - - /** @var UrlFinderInterface $urlFinder */ - $urlFinder = $this->objectManager->get(UrlFinderInterface::class); - $actualUrls = $urlFinder->findOneByData( - [ - 'request_path' => $urlPath, - 'store_id' => $storeId - ] - ); - $categoryId = $actualUrls->getEntityId(); - $targetPath = $actualUrls->getTargetPath(); - $expectedType = $actualUrls->getEntityType(); - - $query = <<<QUERY -{ - urlResolver(url:"/{$urlPath}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals($categoryId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - $this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']); - } - - /** - * Test resolution of '/' path to home page - */ - public function testResolveSlash() - { - /** @var \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfigInterface */ - $scopeConfigInterface = $this->objectManager->get(ScopeConfigInterface::class); - $homePageIdentifier = $scopeConfigInterface->getValue( - PageHelper::XML_PATH_HOME_PAGE, - ScopeInterface::SCOPE_STORE - ); - /** @var \Magento\Cms\Model\Page $page */ - $page = $this->objectManager->get(\Magento\Cms\Model\Page::class); - $page->load($homePageIdentifier); - $homePageId = $page->getId(); - /** @var \Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator $urlPathGenerator */ - $urlPathGenerator = $this->objectManager->get(\Magento\CmsUrlRewrite\Model\CmsPageUrlPathGenerator::class); - /** @param \Magento\Cms\Api\Data\PageInterface $page */ - $targetPath = $urlPathGenerator->getCanonicalUrlPath($page); - $query - = <<<QUERY -{ - urlResolver(url:"/") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals($homePageId, $response['urlResolver']['id']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - $this->assertEquals('CMS_PAGE', $response['urlResolver']['type']); - } - - /** - * Test for custom type which point to the valid product/category/cms page. - * - * @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php - */ - public function testGetNonExistentUrlRewrite() - { - $urlPath = 'non-exist-product.html'; - /** @var UrlRewrite $urlRewrite */ - $urlRewrite = $this->objectManager->create(UrlRewrite::class); - $urlRewrite->load($urlPath, 'request_path'); - - /** @var UrlFinderInterface $urlFinder */ - $urlFinder = $this->objectManager->get(UrlFinderInterface::class); - $actualUrls = $urlFinder->findOneByData( - [ - 'request_path' => $urlPath, - 'store_id' => 1 - ] - ); - $targetPath = $actualUrls->getTargetPath(); - - $query = <<<QUERY -{ - urlResolver(url:"{$urlPath}") - { - id - relative_url - type - } -} -QUERY; - $response = $this->graphQlQuery($query); - $this->assertArrayHasKey('urlResolver', $response); - $this->assertEquals('PRODUCT', $response['urlResolver']['type']); - $this->assertEquals($targetPath, $response['urlResolver']['relative_url']); - } - /** * Test for custom type which point to the invalid product/category/cms page. * From d0773c440b3ae95d83b84969f9e8569a50c77de6 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Tue, 17 Sep 2019 13:30:49 -0500 Subject: [PATCH 836/841] MC-18996: GraphQl Url Resolver doesn't return any results if the url_key doesn't have the extension - fix code review in category class --- .../Model/Resolver/CategoryUrlSuffix.php | 25 +++++++++++-------- .../Model/Resolver/ProductUrlSuffix.php | 13 ++++++---- .../UrlRewriteGraphQl/etc/schema.graphqls | 2 +- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php index ba33f56f472d2..59708d90c23b7 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/CategoryUrlSuffix.php @@ -19,14 +19,19 @@ */ class CategoryUrlSuffix implements ResolverInterface { - const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/category_url_suffix'; + /** + * System setting for the url suffix for categories + * + * @var string + */ + private static $xml_path_category_url_suffix = 'catalog/seo/category_url_suffix'; /** * Cache for product rewrite suffix * * @var array */ - private $cateogryUrlSuffix = []; + private $categoryUrlSuffix = []; /** * @var ScopeConfigInterface @@ -35,11 +40,9 @@ class CategoryUrlSuffix implements ResolverInterface /** * @param ScopeConfigInterface $scopeConfig - * @param array $cateogryUrlSuffix */ - public function __construct(ScopeConfigInterface $scopeConfig, array $cateogryUrlSuffix = []) + public function __construct(ScopeConfigInterface $scopeConfig) { - $this->cateogryUrlSuffix = $cateogryUrlSuffix; $this->scopeConfig = $scopeConfig; } @@ -56,7 +59,7 @@ public function resolve( /** @var StoreInterface $store */ $store = $context->getExtensionAttributes()->getStore(); $storeId = (int)$store->getId(); - return $this->getProductUrlSuffix($storeId); + return $this->getCategoryUrlSuffix($storeId); } /** @@ -65,15 +68,15 @@ public function resolve( * @param int $storeId * @return string */ - private function getProductUrlSuffix(int $storeId): string + private function getCategoryUrlSuffix(int $storeId): string { - if (!isset($this->cateogryUrlSuffix[$storeId])) { - $this->cateogryUrlSuffix[$storeId] = $this->scopeConfig->getValue( - self::XML_PATH_PRODUCT_URL_SUFFIX, + if (!isset($this->categoryUrlSuffix[$storeId])) { + $this->categoryUrlSuffix[$storeId] = $this->scopeConfig->getValue( + self::$xml_path_category_url_suffix, ScopeInterface::SCOPE_STORE, $storeId ); } - return $this->cateogryUrlSuffix[$storeId]; + return $this->categoryUrlSuffix[$storeId]; } } diff --git a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php index 47ac15cc60754..9a0193ba36367 100644 --- a/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php +++ b/app/code/Magento/CatalogUrlRewriteGraphQl/Model/Resolver/ProductUrlSuffix.php @@ -19,7 +19,12 @@ */ class ProductUrlSuffix implements ResolverInterface { - const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/product_url_suffix'; + /** + * System setting for the url suffix for products + * + * @var string + */ + private static $xml_path_product_url_suffix = 'catalog/seo/product_url_suffix'; /** * Cache for product rewrite suffix @@ -35,11 +40,9 @@ class ProductUrlSuffix implements ResolverInterface /** * @param ScopeConfigInterface $scopeConfig - * @param array $productUrlSuffix */ - public function __construct(ScopeConfigInterface $scopeConfig, array $productUrlSuffix = []) + public function __construct(ScopeConfigInterface $scopeConfig) { - $this->productUrlSuffix = $productUrlSuffix; $this->scopeConfig = $scopeConfig; } @@ -69,7 +72,7 @@ private function getProductUrlSuffix(int $storeId): string { if (!isset($this->productUrlSuffix[$storeId])) { $this->productUrlSuffix[$storeId] = $this->scopeConfig->getValue( - self::XML_PATH_PRODUCT_URL_SUFFIX, + self::$xml_path_product_url_suffix, ScopeInterface::SCOPE_STORE, $storeId ); diff --git a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls index e7291aae4b8e2..ace3e0eae831e 100644 --- a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page, using as input a url_key prepended by the url_suffix, if one exists") @cache(cacheIdentity: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite\\UrlResolverIdentity") + urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page, using as input a url_key appended by the url_suffix, if one exists") @cache(cacheIdentity: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\UrlRewrite\\UrlResolverIdentity") } type EntityUrl @doc(description: "EntityUrl is an output object containing the `id`, `relative_url`, and `type` attributes") { From 9ebde0708b28a683511b0de5c73bda5b48aaefc6 Mon Sep 17 00:00:00 2001 From: Cristian Partica <cpartica@magento.com> Date: Tue, 17 Sep 2019 21:12:08 -0500 Subject: [PATCH 837/841] MC-18996: GraphQl Url Resolver doesn't return any results if the url_key doesn't have the extension - fix namespace --- .../Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php index 1b2776e66b7e7..b15cfe3a5b913 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogUrlRewrite/UrlResolverTest.php @@ -5,7 +5,7 @@ */ declare(strict_types=1); -namespace Magento\CatalogGraphQl\UrlRewrite; +namespace Magento\GraphQl\CatalogUrlRewrite; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\TestFramework\ObjectManager; From 43f8d2a48dd73eecc906f88ee6c6a702aed2bded Mon Sep 17 00:00:00 2001 From: Serhii Balko <serhii.balko@transoftgroup.com> Date: Wed, 18 Sep 2019 10:58:39 +0300 Subject: [PATCH 838/841] MC-20139: Wishlist Items of customers not displaying on admin for secondary store --- .../Model/ResourceModel/Item/Collection/Grid.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php index 5d2ab2db3fc77..6ef55bbe81b73 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection/Grid.php @@ -17,14 +17,6 @@ */ class Grid extends \Magento\Wishlist\Model\ResourceModel\Item\Collection { - /** - * Load product attributes to present in grid - */ - private const PRODUCT_ATTRIBUTES_TO_GRID = [ - 'name', - 'price', - ]; - /** * @var \Magento\Framework\Registry */ @@ -120,7 +112,7 @@ protected function _assignProducts() { /** @var ProductCollection $productCollection */ $productCollection = $this->_productCollectionFactory->create() - ->addAttributeToSelect(self::PRODUCT_ATTRIBUTES_TO_GRID) + ->addAttributeToSelect($this->_wishlistConfig->getProductAttributes()) ->addIdFilter($this->_productIds); /** @var Item $item */ From 77562dabf929b7018682d0f5fa70adcf8a89af79 Mon Sep 17 00:00:00 2001 From: Eden <quocviet312@gmail.com> Date: Wed, 18 Sep 2019 17:37:21 +0700 Subject: [PATCH 839/841] Resolve No error message when click "Import Tax Rates" without select file issue24642 --- app/code/Magento/TaxImportExport/i18n/en_US.csv | 2 ++ .../view/adminhtml/templates/importExport.phtml | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/TaxImportExport/i18n/en_US.csv b/app/code/Magento/TaxImportExport/i18n/en_US.csv index 95f94dcfd3b2c..56815947ed1fa 100644 --- a/app/code/Magento/TaxImportExport/i18n/en_US.csv +++ b/app/code/Magento/TaxImportExport/i18n/en_US.csv @@ -18,3 +18,5 @@ Rate,Rate CSV,CSV "Excel XML","Excel XML" "Import/Export Tax Rates","Import/Export Tax Rates" +"Please select a file to import!","Please select a file to import!" + diff --git a/app/code/Magento/TaxImportExport/view/adminhtml/templates/importExport.phtml b/app/code/Magento/TaxImportExport/view/adminhtml/templates/importExport.phtml index 7473612252bb2..1c6b267cd9289 100644 --- a/app/code/Magento/TaxImportExport/view/adminhtml/templates/importExport.phtml +++ b/app/code/Magento/TaxImportExport/view/adminhtml/templates/importExport.phtml @@ -31,7 +31,7 @@ </form> <?php endif; ?> <script> -require(['jquery', "mage/mage", "loadingPopup"], function(jQuery){ +require(['jquery', 'Magento_Ui/js/modal/alert', "mage/mage", "loadingPopup", 'mage/translate'], function(jQuery, uiAlert){ jQuery('#import-form').mage('form').mage('validation'); (function ($) { @@ -42,6 +42,10 @@ require(['jquery', "mage/mage", "loadingPopup"], function(jQuery){ }); $(this.form).submit(); + } else { + uiAlert({ + content: $.mage.__('Please select a file to import!') + }); } }); })(jQuery); From eff29c40e86d81a1849099a1d411c3869f0e575e Mon Sep 17 00:00:00 2001 From: Pavel Bystritsky <engcom-vendorworker-foxtrot@adobe.com> Date: Thu, 19 Sep 2019 15:45:22 +0300 Subject: [PATCH 840/841] magento/magento2#24270: Added MFTF test. --- .../StorefrontProductInfoMainSection.xml | 1 + .../Test/AdminDisablingSwatchTooltipsTest.xml | 160 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 6fdf2276f39d9..5b714f01fd46f 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -16,5 +16,6 @@ <element name="nthSwatchOptionText" type="button" selector="div.swatch-option.text:nth-of-type({{n}})" parameterized="true"/> <element name="productSwatch" type="button" selector="//div[@class='swatch-option'][@aria-label='{{var1}}']" parameterized="true"/> <element name="visualSwatchOption" type="button" selector=".swatch-option[option-tooltip-value='#{{visualSwatchOption}}']" parameterized="true"/> + <element name="swatchOptionTooltip" type="block" selector="div.swatch-option-tooltip"/> </section> </sections> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml new file mode 100644 index 0000000000000..3d69895b0c895 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminDisablingSwatchTooltipsTest.xml @@ -0,0 +1,160 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDisablingSwatchTooltipsTest"> + <annotations> + <features value="Swatches"/> + <title value="Admin disabling swatch tooltips test."/> + <description value="Verify possibility to disable/enable swatch tooltips."/> + <severity value="AVERAGE"/> + <group value="Swatches"/> + </annotations> + <before> + <!-- Create category --> + <createData entity="ApiCategory" stepKey="createCategory"/> + + <!-- Log in --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <!-- Clean up our modifications to the existing color attribute --> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <waitForPageLoad stepKey="waitForProductAttributes"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" + stepKey="fillFilter"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> + <click selector="{{AdminProductAttributeGridSection.AttributeCode('color')}}" stepKey="clickRowToEdit"/> + <click selector="{{AdminManageSwatchSection.nthDelete('1')}}" stepKey="deleteSwatch1"/> + <waitForPageLoad stepKey="waitToClickSave"/> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit"/> + + <!-- Log out --> + <actionGroup ref="logout" stepKey="logOut"/> + + <!-- Delete category --> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + + <!-- Enable swatch tooltips --> + <magentoCLI command="config:set catalog/frontend/show_swatch_tooltip 1" stepKey="disableTooltips"/> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterEnabling"/> + </after> + + <!-- Go to the edit page for the "color" attribute --> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <waitForPageLoad stepKey="waitForProductAttributes"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" + stepKey="fillFilter"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> + <click selector="{{AdminProductAttributeGridSection.AttributeCode('color')}}" stepKey="clickRowToEdit"/> + + <!-- Change to visual swatches --> + <selectOption selector="{{AdminNewAttributePanel.inputType}}" userInput="swatch_visual" + stepKey="selectVisualSwatch"/> + + <!-- Set swatch using the color picker --> + <click selector="{{AdminManageSwatchSection.addSwatch}}" stepKey="clickAddSwatch1"/> + <actionGroup ref="openSwatchMenuByIndex" stepKey="clickSwatch1"> + <argument name="index" value="0"/> + </actionGroup> + <click selector="{{AdminManageSwatchSection.nthChooseColor('1')}}" stepKey="clickChooseColor1"/> + <actionGroup ref="setColorPickerByHex" stepKey="fillHex1"> + <argument name="nthColorPicker" value="1"/> + <argument name="hexColor" value="e74c3c"/> + </actionGroup> + <fillField selector="{{AdminManageSwatchSection.adminInputByIndex('0')}}" userInput="red" stepKey="fillAdmin1"/> + <waitForPageLoad stepKey="waitToClickSave"/> + + <!-- Save --> + <click selector="{{AttributePropertiesSection.SaveAndEdit}}" stepKey="clickSaveAndEdit1"/> + <waitForElementVisible selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccess"/> + + <!-- Assert that the Save was successful after round trip to server --> + <actionGroup ref="assertSwatchColor" stepKey="assertSwatchAdmin"> + <argument name="nthSwatch" value="1"/> + <argument name="expectedStyle" value="background: rgb(231, 77, 60);"/> + </actionGroup> + + <!-- Create a configurable product to verify the storefront with --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductGridPage"/> + <waitForPageLoad stepKey="waitForProductGridPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickOnAddProductToggle"/> + <click selector="{{AdminProductGridActionSection.addConfigurableProduct}}" + stepKey="clickOnAddConfigurableProduct"/> + <fillField userInput="{{_defaultProduct.name}}" selector="{{AdminProductFormSection.productName}}" + stepKey="fillName"/> + <fillField userInput="{{_defaultProduct.sku}}" selector="{{AdminProductFormSection.productSku}}" + stepKey="fillSKU"/> + <fillField userInput="{{_defaultProduct.price}}" selector="{{AdminProductFormSection.productPrice}}" + stepKey="fillPrice"/> + <fillField userInput="{{_defaultProduct.quantity}}" selector="{{AdminProductFormSection.productQuantity}}" + stepKey="fillQuantity"/> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" + parameterArray="[$$createCategory.name$$]" stepKey="fillCategory"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="openSeoSection"/> + <fillField userInput="{{_defaultProduct.urlKey}}" selector="{{AdminProductSEOSection.urlKeyInput}}" + stepKey="fillUrlKey"/> + + <!-- Create configurations based on the Swatch we created earlier --> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" + stepKey="clickCreateConfigurations"/> + <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickFilters"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" userInput="color" + stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAll}}" stepKey="clickOnSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyUniquePricesByAttributeToEachSku}}" + stepKey="clickOnApplyUniquePricesByAttributeToEachSku"/> + <selectOption selector="{{AdminCreateProductConfigurationsPanel.selectAttribute}}" userInput="Color" + stepKey="selectAttributes"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attribute1}}" userInput="10" + stepKey="fillAttributePrice1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" + stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="99" + stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton4"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> + <conditionalClick selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" + dependentSelector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" visible="true" + stepKey="clickOnConfirmInPopup"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage"/> + <seeInTitle userInput="{{_defaultProduct.name}}" stepKey="seeProductNameInTitle"/> + + <!-- Go to the product page and see swatch options --> + <amOnPage url="{{_defaultProduct.urlKey}}.html" stepKey="amOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + + <!-- Verify that the storefront shows the swatches too --> + <actionGroup ref="assertStorefrontSwatchColor" stepKey="assertSwatchStorefront"> + <argument name="nthSwatch" value="1"/> + <argument name="expectedRgb" value="rgb(231, 77, 60)"/> + </actionGroup> + + <!-- Verify swatch tooltips are visible--> + <moveMouseOver selector="{{StorefrontProductInfoMainSection.nthSwatchOption('1')}}" stepKey="hoverEnabledSwatch"/> + <wait time="1" stepKey="waitForTooltip1"/> + <seeElement selector="{{StorefrontProductInfoMainSection.swatchOptionTooltip}}" stepKey="swatchTooltipVisible"/> + + <!-- Disable swatch tooltips --> + <magentoCLI command="config:set catalog/frontend/show_swatch_tooltip 0" stepKey="disableTooltips"/> + <magentoCLI command="cache:flush" stepKey="flushCacheAfterDisabling"/> + + <!-- Verify swatch tooltips are not visible --> + <reloadPage stepKey="refreshPage"/> + <waitForPageLoad stepKey="waitForPageReload"/> + <moveMouseOver selector="{{StorefrontProductInfoMainSection.nthSwatchOption('1')}}" stepKey="hoverDisabledSwatch"/> + <wait time="1" stepKey="waitForTooltip2"/> + <dontSeeElement selector="{{StorefrontProductInfoMainSection.swatchOptionTooltip}}" stepKey="swatchTooltipNotVisible"/> + </test> +</tests> From 70cac4009dece1b6d2d7bb3785ec4f586b2d68d2 Mon Sep 17 00:00:00 2001 From: Stanislav Idolov <sidolov@adobe.com> Date: Thu, 19 Sep 2019 14:37:47 -0500 Subject: [PATCH 841/841] Changed description for the const --- app/code/Magento/Wishlist/Model/Wishlist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Wishlist/Model/Wishlist.php b/app/code/Magento/Wishlist/Model/Wishlist.php index 329f3e591e25b..9b7ff5177afae 100644 --- a/app/code/Magento/Wishlist/Model/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Wishlist.php @@ -53,7 +53,7 @@ class Wishlist extends AbstractModel implements IdentityInterface { /** - * Cache tag + * Wishlist cache tag name */ const CACHE_TAG = 'wishlist';