diff --git a/app/code/Magento/Catalog/Helper/Product/Compare.php b/app/code/Magento/Catalog/Helper/Product/Compare.php index 3a7ce70cbff528239712a353cb5f939f9a0878c4..69f4a613a5b9555e2c46038c2a6cc620b291ef60 100644 --- a/app/code/Magento/Catalog/Helper/Product/Compare.php +++ b/app/code/Magento/Catalog/Helper/Product/Compare.php @@ -231,6 +231,8 @@ class Compare extends \Magento\Framework\Url\Helper\Data $data = [ \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => '', 'product' => $product->getId(), + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove this item from your Compare Products list?') ]; return $this->postHelper->getPostData($this->getRemoveUrl(), $data); } @@ -254,6 +256,8 @@ class Compare extends \Magento\Framework\Url\Helper\Data { $params = [ \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED => '', + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove all items from your Compare Products list?'), ]; return $this->postHelper->getPostData($this->getClearListUrl(), $params); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index 39a67f9722e185894640178329d008de10cb574e..42456a396178c1a1001c45f49a0150612cdd566f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -33,6 +33,11 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb */ protected $_config; + /** + * @var \Magento\Framework\Locale\FormatInterface + */ + private $localeFormat; + /** * Class constructor * @@ -91,8 +96,9 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected function _saveValuePrices(\Magento\Framework\Model\AbstractModel $object) { $priceTable = $this->getTable('catalog_product_option_type_price'); + $formattedPrice = $this->getLocaleFormatter()->getNumber($object->getPrice()); - $price = (double)sprintf('%F', $object->getPrice()); + $price = (double)sprintf('%F', $formattedPrice); $priceType = $object->getPriceType(); if ($object->getPrice() && $priceType) { @@ -410,4 +416,19 @@ class Value extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $object; } + + /** + * Get FormatInterface to convert price from string to number format + * + * @return \Magento\Framework\Locale\FormatInterface + * @deprecated + */ + private function getLocaleFormatter() + { + if ($this->localeFormat === null) { + $this->localeFormat = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Locale\FormatInterface::class); + } + return $this->localeFormat; + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php b/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php index 9f4d41aefd6bb8d1fd02d8990bcdd12c33191035..51388c3725ba9dc430137514ba3c3fc103237d1f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Helper/Product/CompareTest.php @@ -115,7 +115,9 @@ class CompareTest extends \PHPUnit_Framework_TestCase $removeUrl = 'catalog/product_compare/remove'; $postParams = [ Action::PARAM_NAME_URL_ENCODED => '', - 'product' => $productId + 'product' => $productId, + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove this item from your Compare Products list?'), ]; //Verification @@ -156,7 +158,9 @@ class CompareTest extends \PHPUnit_Framework_TestCase //Data $clearUrl = 'catalog/product_compare/clear'; $postParams = [ - Action::PARAM_NAME_URL_ENCODED => '' + Action::PARAM_NAME_URL_ENCODED => '', + 'confirmation' => true, + 'confirmationMessage' => __('Are you sure you want to remove all items from your Compare Products list?'), ]; //Verification diff --git a/app/code/Magento/Catalog/view/frontend/requirejs-config.js b/app/code/Magento/Catalog/view/frontend/requirejs-config.js index c0d05a322b7cd9e0a0603cdbef8505963ead80fa..6c7a8d3a969fff3090e0c24bbb431eebf83b9e6b 100644 --- a/app/code/Magento/Catalog/view/frontend/requirejs-config.js +++ b/app/code/Magento/Catalog/view/frontend/requirejs-config.js @@ -6,7 +6,6 @@ var config = { map: { '*': { - compareItems: 'Magento_Catalog/js/compare', compareList: 'Magento_Catalog/js/list', relatedProducts: 'Magento_Catalog/js/related-products', upsellProducts: 'Magento_Catalog/js/upsell-products', diff --git a/app/code/Magento/Catalog/view/frontend/web/js/compare.js b/app/code/Magento/Catalog/view/frontend/web/js/compare.js deleted file mode 100644 index 66f8767bf2b8830c4229588d7c0a7ac5a8b6a422..0000000000000000000000000000000000000000 --- a/app/code/Magento/Catalog/view/frontend/web/js/compare.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -/*jshint browser:true jquery:true*/ -/*global confirm:true*/ -define([ - "jquery", - "jquery/ui", - "mage/decorate" -], function($){ - "use strict"; - - $.widget('mage.compareItems', { - _create: function() { - this.element.decorate('list', true); - this._confirm(this.options.removeSelector, this.options.removeConfirmMessage); - this._confirm(this.options.clearAllSelector, this.options.clearAllConfirmMessage); - }, - - /** - * Set up a click event on the given selector to display a confirmation request message - * and ask for that confirmation. - * @param selector Selector for the confirmation on click event - * @param message Message to display asking for confirmation to perform action - * @private - */ - _confirm: function(selector, message) { - $(selector).on('click', function() { - return confirm(message); - }); - } - }); - - return $.mage.compareItems; -}); \ No newline at end of file diff --git a/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js b/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js index 5dba397d9bc43d7e149887fe5519c54469d09b0e..9662b3fc7268b7366b9a448e90ca75690506b13a 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js @@ -2,37 +2,32 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ + define([ 'uiComponent', 'Magento_Customer/js/customer-data', - 'mage/translate' -], function (Component, customerData) { + 'jquery', + 'mage/mage', + 'mage/decorate' +], function (Component, customerData, $) { 'use strict'; var sidebarInitialized = false; + /** + * Initialize sidebar + */ function initSidebar() { if (sidebarInitialized) { return; } - sidebarInitialized = true; - require([ - 'jquery', - 'mage/mage' - ], function ($) { - /*eslint-disable max-len*/ - $('[data-role=compare-products-sidebar]').mage('compareItems', { - 'removeConfirmMessage': $.mage.__('Are you sure you want to remove this item from your Compare Products list?'), - 'removeSelector': '#compare-items a.action.delete', - 'clearAllConfirmMessage': $.mage.__('Are you sure you want to remove all items from your Compare Products list?'), - 'clearAllSelector': '#compare-clear-all' - }); - /*eslint-enable max-len*/ - }); + sidebarInitialized = true; + $('[data-role=compare-products-sidebar]').decorate('list', true); } return Component.extend({ + /** @inheritdoc */ initialize: function () { this._super(); this.compareProducts = customerData.get('compare-products'); diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php index c4d9657a322dab4b48a1668f908021de223f050b..d176b5be6a4551d9d7162ebf5d9436f99aadcd37 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php @@ -151,25 +151,28 @@ class Tablerate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implemen $request->setPackageQty($oldQty); if (!empty($rate) && $rate['price'] >= 0) { - /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */ - $method = $this->_resultMethodFactory->create(); - - $method->setCarrier('tablerate'); - $method->setCarrierTitle($this->getConfigData('title')); - - $method->setMethod('bestway'); - $method->setMethodTitle($this->getConfigData('name')); - if ($request->getFreeShipping() === true || $request->getPackageQty() == $freeQty) { $shippingPrice = 0; } else { $shippingPrice = $this->getFinalPriceWithHandlingFee($rate['price']); } - - $method->setPrice($shippingPrice); - $method->setCost($rate['cost']); - + $method = $this->createShippingMethod($shippingPrice, $rate['cost']); $result->append($method); + } elseif (empty($rate) && $request->getFreeShipping() === true || $request->getPackageQty() == $freeQty) { + + /** + * Promotion rule was applied for the whole cart. + * In this case all other shipping methods could be omitted + * Table rate shipping method with 0$ price must be shown if grand total is more than minimal value. + * Free package weight has been already taken into account. + */ + $request->setPackageValue($freePackageValue); + $request->setPackageQty($freeQty); + $rate = $this->getRate($request); + if (!empty($rate) && $rate['price'] >= 0) { + $method = $this->createShippingMethod(0, 0); + $result->append($method); + } } else { /** @var \Magento\Quote\Model\Quote\Address\RateResult\Error $error */ $error = $this->_rateErrorFactory->create( @@ -241,4 +244,27 @@ class Tablerate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implemen { return ['bestway' => $this->getConfigData('name')]; } + + /** + * Get the method object based on the shipping price and cost + * + * @param float $shippingPrice + * @param float $cost + * @return \Magento\Quote\Model\Quote\Address\RateResult\Method + */ + private function createShippingMethod($shippingPrice, $cost) + { + /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */ + $method = $this->_resultMethodFactory->create(); + + $method->setCarrier('tablerate'); + $method->setCarrierTitle($this->getConfigData('title')); + + $method->setMethod('bestway'); + $method->setMethodTitle($this->getConfigData('name')); + + $method->setPrice($shippingPrice); + $method->setCost($cost); + return $method; + } } diff --git a/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php b/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php index 7685fc0fed94df32b0e0638e65d6d15f6de1d5ce..b93cb70fdda3d9cc9b6d8d9f7bb565d576392de4 100644 --- a/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php +++ b/app/code/Magento/Review/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ReviewTest.php @@ -8,6 +8,8 @@ namespace Magento\Review\Test\Unit\Ui\DataProvider\Product\Form\Modifier; use Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier\AbstractModifierTest; use Magento\Framework\UrlInterface; use Magento\Review\Ui\DataProvider\Product\Form\Modifier\Review; +use Magento\Framework\Module\Manager as ModuleManager; +use Magento\Ui\DataProvider\Modifier\ModifierInterface; /** * Class ReviewTest @@ -19,36 +21,73 @@ class ReviewTest extends AbstractModifierTest */ protected $urlBuilderMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $moduleManagerMock; + protected function setUp() { parent::setUp(); $this->urlBuilderMock = $this->getMockBuilder(UrlInterface::class) ->getMockForAbstractClass(); + $this->moduleManagerMock = $this->getMock(ModuleManager::class, [], [], '', false); } + /** + * @return ModifierInterface + */ protected function createModel() { - return $this->objectManager->getObject(Review::class, [ + $model = $this->objectManager->getObject(Review::class, [ 'locator' => $this->locatorMock, 'urlBuilder' => $this->urlBuilderMock, ]); + + $reviewClass = new \ReflectionClass(Review::class); + $moduleManagerProperty = $reviewClass->getProperty('moduleManager'); + $moduleManagerProperty->setAccessible(true); + $moduleManagerProperty->setValue( + $model, + $this->moduleManagerMock + ); + + return $model; } - public function testModifyMetaToBeEmpty() + public function testModifyMetaDoesNotAddReviewSectionForNewProduct() + { + $this->productMock->expects($this->once()) + ->method('getId'); + + $this->assertSame([], $this->getModel()->modifyMeta([])); + } + + public function testModifyMetaDoesNotAddReviewSectionIfReviewModuleOutputIsDisabled() { $this->productMock->expects($this->once()) ->method('getId') - ->willReturn(0); + ->willReturn(1); + + $this->moduleManagerMock->expects($this->any()) + ->method('isOutputEnabled') + ->with('Magento_Review') + ->willReturn(false); $this->assertSame([], $this->getModel()->modifyMeta([])); } - public function testModifyMeta() + public function testModifyMetaAddsReviewSectionForExistingProductIfReviewModuleOutputIsEnabled() { $this->productMock->expects($this->once()) ->method('getId') ->willReturn(1); + $this->moduleManagerMock->expects($this->any()) + ->method('isOutputEnabled') + ->with('Magento_Review') + ->willReturn(true); + $this->assertArrayHasKey(Review::GROUP_REVIEW, $this->getModel()->modifyMeta([])); } diff --git a/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php b/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php index 82141b5ab2f12a03e44463555e7b0a302630b0d2..0ef1057eb4be7ffd23fe794bef67663dc247b5c6 100644 --- a/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php +++ b/app/code/Magento/Review/Ui/DataProvider/Product/Form/Modifier/Review.php @@ -12,6 +12,8 @@ use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Ui\Component\Form; use Magento\Framework\UrlInterface; +use Magento\Framework\Module\Manager as ModuleManager; +use Magento\Framework\App\ObjectManager; /** * Class Review @@ -34,6 +36,11 @@ class Review extends AbstractModifier */ protected $urlBuilder; + /** + * @var ModuleManager + */ + private $moduleManager; + /** * @param LocatorInterface $locator * @param UrlInterface $urlBuilder @@ -51,7 +58,7 @@ class Review extends AbstractModifier */ public function modifyMeta(array $meta) { - if (!$this->locator->getProduct()->getId()) { + if (!$this->locator->getProduct()->getId() || !$this->getModuleManager()->isOutputEnabled('Magento_Review')) { return $meta; } @@ -114,4 +121,19 @@ class Review extends AbstractModifier return $data; } + + /** + * Retrieve module manager instance using dependency lookup to keep this class backward compatible. + * + * @return ModuleManager + * + * @deprecated + */ + private function getModuleManager() + { + if ($this->moduleManager === null) { + $this->moduleManager = ObjectManager::getInstance()->get(ModuleManager::class); + } + return $this->moduleManager; + } } diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml index 4b53e0ccb1e9fefd91ea23c555dc64c7f617ca72..fe212490a2fd00c9e18fc6fea8ab28b01e34e45d 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form.phtml @@ -8,7 +8,7 @@ /** @var \Magento\Sales\Block\Adminhtml\Order\Create\Form $block */ ?> -<form id="edit_form" data-order-config='<?php /* @escapeNotVerified */ echo $block->getOrderDataJson() ?>' data-load-base-url="<?php /* @escapeNotVerified */ echo $block->getLoadBlockUrl() ?>" action="<?php /* @escapeNotVerified */ echo $block->getSaveUrl() ?>" method="post" enctype="multipart/form-data"> +<form id="edit_form" data-order-config='<?php echo $block->escapeHtml($block->getOrderDataJson()) ?>' data-load-base-url="<?php /* @escapeNotVerified */ echo $block->getLoadBlockUrl() ?>" action="<?php /* @escapeNotVerified */ echo $block->getSaveUrl() ?>" method="post" enctype="multipart/form-data"> <?php echo $block->getBlockHtml('formkey')?> <div id="order-message"> <?php echo $block->getChildHtml('message') ?> diff --git a/app/code/Magento/SalesRule/etc/di.xml b/app/code/Magento/SalesRule/etc/di.xml index 2c731823b778fabe6faab37024f4e9ea60247538..83364f924397d857030a9f039ed1ab59dc0fd5f0 100644 --- a/app/code/Magento/SalesRule/etc/di.xml +++ b/app/code/Magento/SalesRule/etc/di.xml @@ -78,7 +78,7 @@ <type name="Magento\Framework\Model\Entity\RepositoryFactory"> <arguments> <argument name="entities" xsi:type="array"> - <item name="Magento\SalesRule\Api\Data\RuleInterface" xsi:type="string">Magento\SalesRule\Api\RuleRepositoryInterface</item> + <item name="Magento\SalesRule\Api\Data\RuleInterface" xsi:type="string">Magento\SalesRule\Model\ResourceModel\Rule</item> </argument> </arguments> </type> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php index 546b055b0045b29d6cfe216b1b0efdc7407ebc0a..4de51f3e2ca9b8d5089cb6818d380f0ac9465e46 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/ListCompare.php @@ -86,6 +86,13 @@ class ListCompare extends Block */ protected $messageBlock = '#messages'; + /** + * Selector for confirm. + * + * @var string + */ + protected $confirmModal = '.confirm._show[data-role=modal]'; + /** * Get product info. * @@ -189,6 +196,13 @@ class ListCompare extends Block public function removeProduct($index = 1) { $this->_rootElement->find(sprintf($this->removeButton, $index), Locator::SELECTOR_XPATH)->click(); + $modalElement = $this->browser->find($this->confirmModal); + /** @var \Magento\Ui\Test\Block\Adminhtml\Modal $modal */ + $modal = $this->blockFactory->create( + \Magento\Ui\Test\Block\Adminhtml\Modal::class, + ['element' => $modalElement] + ); + $modal->acceptAlert(); } /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php index 596dbd38664b3b097302e6f6f0e62d13fbe96017..ab65865af6534c2b2478490b7f08fd7f108496fe 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/Compare/Sidebar.php @@ -32,6 +32,13 @@ class Sidebar extends ListCompare */ protected $clearAll = '#compare-clear-all'; + /** + * Selector for confirm. + * + * @var string + */ + protected $confirmModal = '.confirm._show[data-role=modal]'; + /** * Get compare products block content. * @@ -79,5 +86,12 @@ class Sidebar extends ListCompare } ); $this->_rootElement->find($this->clearAll)->click(); + $modalElement = $this->browser->find($this->confirmModal); + /** @var \Magento\Ui\Test\Block\Adminhtml\Modal $modal */ + $modal = $this->blockFactory->create( + \Magento\Ui\Test\Block\Adminhtml\Modal::class, + ['element' => $modalElement] + ); + $modal->acceptAlert(); } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index f610ee5e15473c89cc2f4fe4f6f3cc9e92a48eac..3c27a65c4a74385f546c108c077c128ebb4edcfb 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -476,13 +476,24 @@ class ProductTest extends \PHPUnit_Framework_TestCase } /** - * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_options.php * @magentoAppIsolation enabled */ public function testGetOptions() { - $this->_model = $this->productRepository->get('simple'); - - $this->assertEquals(4, count($this->_model->getOptions())); + $this->_model = $this->productRepository->get('simple_with_custom_options'); + $options = $this->_model->getOptions(); + $this->assertNotEmpty($options); + $expectedValue = [ + '3-1-select' => 3000.00, + '3-2-select' => 5000.00, + '4-1-radio' => 600.234, + '4-2-radio' => 40000.00 + ]; + foreach ($options as $option) { + foreach ($option->getValues() as $value) { + $this->assertEquals($expectedValue[$value->getSku()], floatval($value->getPrice())); + } + } } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php new file mode 100644 index 0000000000000000000000000000000000000000..0366e90cd9772036315678f6e3b10d1f59b615fb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); + +/** @var \Magento\TestFramework\ObjectManager $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Catalog\Api\CategoryLinkManagementInterface $categoryLinkManagement */ +$categoryLinkManagement = $objectManager->create(\Magento\Catalog\Api\CategoryLinkManagementInterface::class); + +/** @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(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple_with_custom_options') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription("Short description") + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->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( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ] + )->setCanSaveCustomOptions(true) + ->setHasOptions(true); + +$oldOptions = [ + [ + 'previous_group' => 'select', + 'title' => 'Test Select', + 'type' => 'drop_down', + 'is_require' => 1, + 'sort_order' => 0, + 'values' => [ + [ + 'option_type_id' => -1, + 'title' => 'Option 1', + 'price' => '3,000.00', + 'price_type' => 'fixed', + 'sku' => '3-1-select', + ], + [ + 'option_type_id' => -1, + 'title' => 'Option 2', + 'price' => '5,000.00', + 'price_type' => 'fixed', + 'sku' => '3-2-select', + ], + ] + ], + [ + 'previous_group' => 'select', + 'title' => 'Test Radio', + 'type' => 'radio', + 'is_require' => 1, + 'sort_order' => 0, + 'values' => [ + [ + 'option_type_id' => -1, + 'title' => 'Option 1', + 'price' => '600.234', + 'price_type' => 'fixed', + 'sku' => '4-1-radio', + ], + [ + 'option_type_id' => -1, + 'title' => 'Option 2', + 'price' => '40,000.00', + 'price_type' => 'fixed', + 'sku' => '4-2-radio', + ], + ] + ] +]; + +$options = []; + +/** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory */ +$customOptionFactory = $objectManager->create(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); + +foreach ($oldOptions as $option) { + /** @var \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option */ + $option = $customOptionFactory->create(['data' => $option]); + $option->setProductSku($product->getSku()); + + $options[] = $option; +} + +$product->setOptions($options); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepositoryFactory */ +$productRepositoryFactory = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepositoryFactory->save($product); + +$categoryLinkManagement->assignProductToCategories( + $product->getSku(), + [2] +); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..88e0fb198576060eb8295e633a985d9d6e3ff96a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_options_rollback.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +use Magento\Framework\Exception\NoSuchEntityException; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->getInstance()->reinitialize(); + +/** @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 = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); +try { + $product = $productRepository->get('simple_with_custom_options', false, null, true); + $productRepository->delete($product); +} catch (NoSuchEntityException $e) { + +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates.php new file mode 100644 index 0000000000000000000000000000000000000000..5cac40809565e1ed28df9529446550975b39f9f3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +$connection = $resource->getConnection(); +$resourceModel = $objectManager->create(\Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate::class); +$entityTable = $resourceModel->getTable('shipping_tablerate'); +$data = + [ + 'website_id' => 1, + 'dest_country_id' => 'US', + 'dest_region_id' => 0, + 'dest_zip' => '*', + 'condition_name' => 'package_qty', + 'condition_value' => 1, + 'price' => 10, + 'cost' => 10 + ]; +$connection->query( + "INSERT INTO {$entityTable} (`website_id`, `dest_country_id`, `dest_region_id`, `dest_zip`, `condition_name`," + . "`condition_value`, `price`, `cost`) VALUES (:website_id, :dest_country_id, :dest_region_id, :dest_zip," + . " :condition_name, :condition_value, :price, :cost);", + $data +); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_rollback.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..10f5563eee8aae9c826a8967e93658b59d9c2eca --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_rollback.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +$connection = $resource->getConnection(); +$resourceModel = $objectManager->create(\Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate::class); +$entityTable = $resourceModel->getTable('shipping_tablerate'); +$connection->query("DELETE FROM {$entityTable};"); diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7ed966d0c9b998a23145bde4f2432019628a3c6f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/ShippingMethodManagementTest.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Quote\Model; + +/** + * Class ShippingMethodManagementTest + */ +class ShippingMethodManagementTest extends \PHPUnit_Framework_TestCase +{ + + /** + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoConfigFixture current_store carriers/tablerate/active 1 + * @magentoConfigFixture current_store carriers/tablerate/condition_name package_qty + * @magentoDataFixture Magento/SalesRule/_files/cart_rule_free_shipping.php + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/OfflineShipping/_files/tablerates.php + */ + public function testEstimateByAddressWithCartPriceRule() + { + $this->executeTestFlow(0, 0); + } + + /** + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoConfigFixture current_store carriers/tablerate/active 1 + * @magentoConfigFixture current_store carriers/tablerate/condition_name package_qty + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/OfflineShipping/_files/tablerates.php + */ + public function testEstimateByAddress() + { + $this->executeTestFlow(5, 10); + } + + /** + * Provide testing of shipping method estimation based on address + * + * @param int $flatRateAmount + * @param int $tableRateAmount + */ + private function executeTestFlow($flatRateAmount, $tableRateAmount) + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $objectManager->get(\Magento\Quote\Model\Quote::class); + $quote->load('test01', 'reserved_order_id'); + $cartId = $quote->getId(); + if (!$cartId) { + $this->fail('quote fixture failed'); + } + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ + $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); + $quoteIdMask->load($cartId, 'quote_id'); + //Use masked cart Id + $cartId = $quoteIdMask->getMaskedId(); + $data = [ + 'data' => [ + 'country_id' => "US", + 'postcode' => null, + 'region' => null, + 'region_id' => null + ] + ]; + /** @var \Magento\Quote\Api\Data\EstimateAddressInterface $address */ + $address = $objectManager->create(\Magento\Quote\Api\Data\EstimateAddressInterface::class, $data); + /** @var \Magento\Quote\Api\GuestShippingMethodManagementInterface $shippingEstimation */ + $shippingEstimation = $objectManager->get(\Magento\Quote\Api\GuestShippingMethodManagementInterface::class); + $result = $shippingEstimation->estimateByAddress($cartId, $address); + $this->assertNotEmpty($result); + $expectedResult = [ + 'tablerate' => + [ + 'method_code' => 'bestway', + 'amount' => $tableRateAmount + ], + 'flatrate' => [ + 'method_code' => 'flatrate', + 'amount' => $flatRateAmount + ] + ]; + foreach ($result as $rate) { + $this->assertEquals($expectedResult[$rate->getCarrierCode()]['amount'], $rate->getAmount()); + $this->assertEquals($expectedResult[$rate->getCarrierCode()]['method_code'], $rate->getMethodCode()); + } + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt b/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt index d58f71f3fbc9e1635f1c8f8ee529a9a5e73214a0..29b5280ec6693f70c8d65a1f40e25c644897ace1 100644 --- a/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt +++ b/dev/tests/static/testsuite/Magento/Test/Js/_files/blacklist/magento.txt @@ -41,13 +41,11 @@ app/code/Magento/Catalog/view/base/web/js/price-utils.js app/code/Magento/Catalog/view/base/web/js/tier-price.js app/code/Magento/Catalog/view/frontend/requirejs-config.js app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js -app/code/Magento/Catalog/view/frontend/web/js/compare.js app/code/Magento/Catalog/view/frontend/web/js/gallery.js app/code/Magento/Catalog/view/frontend/web/js/list.js app/code/Magento/Catalog/view/frontend/web/js/product/list/toolbar.js app/code/Magento/Catalog/view/frontend/web/js/related-products.js app/code/Magento/Catalog/view/frontend/web/js/upsell-products.js -app/code/Magento/Catalog/view/frontend/web/js/view/compare-products.js app/code/Magento/Catalog/view/frontend/web/js/view/image.js app/code/Magento/Catalog/view/frontend/web/js/zoom.js app/code/Magento/Catalog/view/frontend/web/product/view/validation.js @@ -511,7 +509,6 @@ lib/web/mage/captcha.js lib/web/mage/collapsible.js lib/web/mage/common.js lib/web/mage/cookies.js -lib/web/mage/dataPost.js lib/web/mage/decorate.js lib/web/mage/deletable-item.js lib/web/mage/dialog.js @@ -603,13 +600,11 @@ vendor/magento/module-catalog/view/base/web/js/price-utils.js vendor/magento/module-catalog/view/base/web/js/tier-price.js vendor/magento/module-catalog/view/frontend/requirejs-config.js vendor/magento/module-catalog/view/frontend/web/js/catalog-add-to-cart.js -vendor/magento/module-catalog/view/frontend/web/js/compare.js vendor/magento/module-catalog/view/frontend/web/js/gallery.js vendor/magento/module-catalog/view/frontend/web/js/list.js vendor/magento/module-catalog/view/frontend/web/js/product/list/toolbar.js vendor/magento/module-catalog/view/frontend/web/js/related-products.js vendor/magento/module-catalog/view/frontend/web/js/upsell-products.js -vendor/magento/module-catalog/view/frontend/web/js/view/compare-products.js vendor/magento/module-catalog/view/frontend/web/js/view/image.js vendor/magento/module-catalog/view/frontend/web/js/zoom.js vendor/magento/module-catalog/view/frontend/web/product/view/validation.js diff --git a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php index befff770156178dd2324e42365b62ac52414c9e8..ecfb67320cb0d58decdf67de64d2058814f1eaf8 100644 --- a/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php +++ b/lib/internal/Magento/Framework/Interception/PluginList/PluginList.php @@ -75,6 +75,11 @@ class PluginList extends Scoped implements InterceptionPluginList */ protected $_pluginInstances = []; + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + /** * @param ReaderInterface $reader * @param ScopeInterface $configScope @@ -149,6 +154,7 @@ class PluginList extends Scoped implements InterceptionPluginList } $this->_inherited[$type] = null; if (is_array($plugins) && count($plugins)) { + $this->filterPlugins($plugins); uasort($plugins, [$this, '_sort']); $this->trimInstanceStartingBackslash($plugins); $this->_inherited[$type] = $plugins; @@ -348,4 +354,34 @@ class PluginList extends Scoped implements InterceptionPluginList } } } + + /** + * Remove from list not existing plugins + * + * @param array $plugins + * @return void + */ + private function filterPlugins(array &$plugins) + { + foreach ($plugins as $name => $plugin) { + if (empty($plugin['instance'])) { + unset($plugins[$name]); + $this->getLogger()->info("Reference to undeclared plugin with name '{$name}'."); + } + } + } + + /** + * Returns logger instance + * + * @deprecated + * @return \Psr\Log\LoggerInterface + */ + private function getLogger() + { + if ($this->logger === null) { + $this->logger = $this->_objectManager->get(\Psr\Log\LoggerInterface::class); + } + return $this->logger; + } } diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php index 1291ae44bce6522bf61f1bdbc42c3089a09abd48..b3fe011a0a490727c0682f1863aa731622753264 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/PluginList/PluginListTest.php @@ -60,7 +60,6 @@ class PluginListTest extends \PHPUnit_Framework_TestCase $omConfigMock->expects($this->any())->method('getOriginalInstanceType')->will($this->returnArgument(0)); $this->_objectManagerMock = $this->getMock(\Magento\Framework\ObjectManagerInterface::class); - $this->_objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); $definitions = new \Magento\Framework\ObjectManager\Definition\Runtime(); @@ -80,6 +79,7 @@ class PluginListTest extends \PHPUnit_Framework_TestCase public function testGetPlugin() { + $this->_objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); $this->_configScopeMock->expects($this->any())->method('getCurrentScope')->will($this->returnValue('backend')); $this->_model->getNext(\Magento\Framework\Interception\Test\Unit\Custom\Module\Model\Item::class, 'getName'); $this->_model->getNext( @@ -131,6 +131,7 @@ class PluginListTest extends \PHPUnit_Framework_TestCase */ public function testGetPlugins($expectedResult, $type, $method, $scopeCode, $code = '__self') { + $this->_objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); $this->_configScopeMock->expects( $this->any() )->method( @@ -206,6 +207,7 @@ class PluginListTest extends \PHPUnit_Framework_TestCase */ public function testInheritPluginsWithNonExistingClass() { + $this->_objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); $this->_configScopeMock->expects($this->any()) ->method('getCurrentScope') ->will($this->returnValue('frontend')); @@ -213,12 +215,34 @@ class PluginListTest extends \PHPUnit_Framework_TestCase $this->_model->getNext('SomeType', 'someMethod'); } + /** + * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext + * @covers \Magento\Framework\Interception\PluginList\PluginList::_inheritPlugins + */ + public function testInheritPluginsWithNotExistingPlugin() + { + $loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); + $this->_objectManagerMock->expects($this->once()) + ->method('get') + ->with(\Psr\Log\LoggerInterface::class) + ->willReturn($loggerMock); + $loggerMock->expects($this->once()) + ->method('info') + ->with("Reference to undeclared plugin with name 'simple_plugin'."); + $this->_configScopeMock->expects($this->any()) + ->method('getCurrentScope') + ->will($this->returnValue('frontend')); + + $this->assertNull($this->_model->getNext('typeWithoutInstance', 'someMethod')); + } + /** * @covers \Magento\Framework\Interception\PluginList\PluginList::getNext * @covers \Magento\Framework\Interception\PluginList\PluginList::_loadScopedData */ public function testLoadScopedDataCached() { + $this->_objectManagerMock->expects($this->any())->method('get')->will($this->returnArgument(0)); $this->_configScopeMock->expects($this->once()) ->method('getCurrentScope') ->will($this->returnValue('scope')); diff --git a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php index 832a5a67599da7f9dbfe46e2732324b536f267c8..87bbe0d35dd2561799cd5d2024347c8fb48c2b73 100644 --- a/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php +++ b/lib/internal/Magento/Framework/Interception/Test/Unit/_files/reader_mock_map.php @@ -70,6 +70,11 @@ return [ 'instance' => 'NonExistingPluginClass', ], ], + ], + 'typeWithoutInstance' => [ + 'plugins' => [ + 'simple_plugin' => [], + ], ] ] ] diff --git a/lib/web/mage/dataPost.js b/lib/web/mage/dataPost.js index e1c297ffcef4b9a6a3d64806d5f93eb57e938644..2d47929f57213c338c5b121310c544b3876331ec 100644 --- a/lib/web/mage/dataPost.js +++ b/lib/web/mage/dataPost.js @@ -2,50 +2,88 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ -/*jshint browser:true jquery:true*/ -/*global confirm:true*/ + define([ - "jquery", - "mage/template", - "jquery/ui" -], function($,mageTemplate){ - + 'jquery', + 'mage/template', + 'Magento_Ui/js/modal/confirm', + 'jquery/ui' +], function ($, mageTemplate, uiConfirm) { + 'use strict'; + $.widget('mage.dataPost', { options: { - formTemplate: '<form action="<%- data.action %>" method="post">' - + '<% _.each(data.data, function(value, index) { %>' - + '<input name="<%- index %>" value="<%- value %>">' - + '<% }) %></form>', + formTemplate: '<form action="<%- data.action %>" method="post">' + + '<% _.each(data.data, function(value, index) { %>' + + '<input name="<%- index %>" value="<%- value %>">' + + '<% }) %></form>', postTrigger: ['a[data-post]', 'button[data-post]', 'span[data-post]'], formKeyInputSelector: 'input[name="form_key"]' }, - _create: function() { + + /** @inheritdoc */ + _create: function () { this._bind(); }, - _bind: function() { + + /** @inheritdoc */ + _bind: function () { var events = {}; - $.each(this.options.postTrigger, function(index, value) { + + $.each(this.options.postTrigger, function (index, value) { events['click ' + value] = '_postDataAction'; }); + this._on(events); }, - _postDataAction: function(e) { - e.preventDefault(); + + /** + * Handler for click. + * + * @param {Object} e + * @private + */ + _postDataAction: function (e) { var params = $(e.currentTarget).data('post'); + + e.preventDefault(); this.postData(params); }, - postData: function(params) { - var formKey = $(this.options.formKeyInputSelector).val(); + + /** + * Data post action. + * + * @param {Object} params + */ + postData: function (params) { + var formKey = $(this.options.formKeyInputSelector).val(), + $form; + if (formKey) { - params.data.form_key = formKey; + params.data['form_key'] = formKey; } - $(mageTemplate(this.options.formTemplate, { + + $form = $(mageTemplate(this.options.formTemplate, { data: params - })).appendTo('body').hide().submit(); + })); + + if (params.data.confirmation) { + uiConfirm({ + content: params.data.confirmationMessage, + actions: { + /** @inheritdoc */ + confirm: function () { + $form.appendTo('body').hide().submit(); + } + } + }); + } else { + $form.appendTo('body').hide().submit(); + } } }); - + $(document).dataPost(); return $.mage.dataPost; -}); \ No newline at end of file +});