diff --git a/.gitignore b/.gitignore
index 94c3bf76a2bd1..75e5f11d8a8e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,7 +33,6 @@ atlassian*
/.php_cs
/.php_cs.cache
/grunt-config.json
-/dev/tools/grunt/configs/local-themes.js
/pub/media/*.*
!/pub/media/.htaccess
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 c60504f2c44ce..fd096f7a372b6 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
@@ -7,7 +7,6 @@
define([
'jquery',
'underscore',
- 'mage/utils/wrapper',
'Magento_Checkout/js/view/payment/default',
'Magento_Braintree/js/view/payment/adapter',
'Magento_Checkout/js/model/quote',
@@ -19,7 +18,6 @@ define([
], function (
$,
_,
- wrapper,
Component,
Braintree,
quote,
@@ -105,6 +103,12 @@ define([
}
});
+ quote.shippingAddress.subscribe(function () {
+ if (self.isActive()) {
+ self.reInitPayPal();
+ }
+ });
+
// for each component initialization need update property
this.isReviewRequired(false);
this.initClientConfig();
@@ -222,9 +226,8 @@ define([
/**
* Re-init PayPal Auth Flow
- * @param {Function} callback - Optional callback
*/
- reInitPayPal: function (callback) {
+ reInitPayPal: function () {
if (Braintree.checkout) {
Braintree.checkout.teardown(function () {
Braintree.checkout = null;
@@ -235,17 +238,6 @@ define([
this.clientConfig.paypal.amount = this.grandTotalAmount;
this.clientConfig.paypal.shippingAddressOverride = this.getShippingAddress();
- if (callback) {
- this.clientConfig.onReady = wrapper.wrap(
- this.clientConfig.onReady,
- function (original, checkout) {
- this.clientConfig.onReady = original;
- original(checkout);
- callback();
- }.bind(this)
- );
- }
-
Braintree.setConfig(this.clientConfig);
Braintree.setup();
},
@@ -429,19 +421,17 @@ define([
* Triggers when customer click "Continue to PayPal" button
*/
payWithPayPal: function () {
- this.reInitPayPal(function () {
- if (!additionalValidators.validate()) {
- return;
- }
+ if (!additionalValidators.validate()) {
+ return;
+ }
- try {
- Braintree.checkout.paypal.initAuthFlow();
- } catch (e) {
- this.messageContainer.addErrorMessage({
- message: $t('Payment ' + this.getTitle() + ' can\'t be initialized.')
- });
- }
- }.bind(this));
+ try {
+ Braintree.checkout.paypal.initAuthFlow();
+ } catch (e) {
+ this.messageContainer.addErrorMessage({
+ message: $t('Payment ' + this.getTitle() + ' can\'t be initialized.')
+ });
+ }
},
/**
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
index 0c547f81c85d6..043704a9f2bca 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
@@ -25,7 +25,7 @@ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getCrossSellProductCollection()->addAttributeToSelect(
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
index 3f9dac98033aa..efa4fe4330c84 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
@@ -82,7 +82,7 @@ public function __construct(
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getRelatedProductCollection()->addAttributeToSelect(
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
index 726f083c07cd4..d3a73a94dce17 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
@@ -97,7 +97,7 @@ public function __construct(
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getUpSellProductCollection()->setPositionOrder()->addStoreFilter();
if ($this->moduleManager->isEnabled('Magento_Checkout')) {
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare.php b/app/code/Magento/Catalog/Controller/Product/Compare.php
index 1ee146e5aaa70..0a65a1bd42c3d 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare.php
@@ -139,4 +139,12 @@ public function setCustomerId($customerId)
$this->_customerId = $customerId;
return $this;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function execute()
+ {
+ return $this->resultRedirectFactory->create()->setPath('catalog/product_compare');
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php
index 73d0f1fa6795b..bf7bfbe681929 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php
@@ -5,9 +5,10 @@
*/
namespace Magento\Catalog\Model\Product\Gallery;
+use Magento\Framework\Api\Data\ImageContentInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Filesystem\DriverInterface;
+use Magento\Framework\App\ObjectManager;
/**
* Catalog product Media Gallery attribute processor.
@@ -55,28 +56,39 @@ class Processor
*/
protected $resourceModel;
+ /**
+ * @var \Magento\Framework\File\Mime
+ */
+ private $mime;
+
/**
* @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
* @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb
* @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel
+ * @param \Magento\Framework\File\Mime|null $mime
+ * @throws \Magento\Framework\Exception\FileSystemException
*/
public function __construct(
\Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
\Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb,
\Magento\Catalog\Model\Product\Media\Config $mediaConfig,
\Magento\Framework\Filesystem $filesystem,
- \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel
+ \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel,
+ \Magento\Framework\File\Mime $mime = null
) {
$this->attributeRepository = $attributeRepository;
$this->fileStorageDb = $fileStorageDb;
$this->mediaConfig = $mediaConfig;
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->resourceModel = $resourceModel;
+ $this->mime = $mime ?: ObjectManager::getInstance()->get(\Magento\Framework\File\Mime::class);
}
/**
+ * Return media_gallery attribute
+ *
* @return \Magento\Catalog\Api\Data\ProductAttributeInterface
* @since 101.0.0
*/
@@ -178,6 +190,13 @@ public function addImage(
$attrCode = $this->getAttribute()->getAttributeCode();
$mediaGalleryData = $product->getData($attrCode);
$position = 0;
+
+ $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($file);
+ $imageMimeType = $this->mime->getMimeType($absoluteFilePath);
+ $imageContent = $this->mediaDirectory->readFile($absoluteFilePath);
+ $imageBase64 = base64_encode($imageContent);
+ $imageName = $pathinfo['filename'];
+
if (!is_array($mediaGalleryData)) {
$mediaGalleryData = ['images' => []];
}
@@ -192,9 +211,17 @@ public function addImage(
$mediaGalleryData['images'][] = [
'file' => $fileName,
'position' => $position,
- 'media_type' => 'image',
'label' => '',
'disabled' => (int)$exclude,
+ 'media_type' => 'image',
+ 'types' => $mediaAttribute,
+ 'content' => [
+ 'data' => [
+ ImageContentInterface::NAME => $imageName,
+ ImageContentInterface::BASE64_ENCODED_DATA => $imageBase64,
+ ImageContentInterface::TYPE => $imageMimeType,
+ ]
+ ]
];
$product->setData($attrCode, $mediaGalleryData);
@@ -353,7 +380,8 @@ public function setMediaAttribute(\Magento\Catalog\Model\Product $product, $medi
}
/**
- * get media attribute codes
+ * Get media attribute codes
+ *
* @return array
* @since 101.0.0
*/
@@ -363,6 +391,8 @@ public function getMediaAttributeCodes()
}
/**
+ * Trim .tmp ending from filename
+ *
* @param string $file
* @return string
* @since 101.0.0
@@ -484,7 +514,6 @@ public function getAffectedFields($object)
/**
* Attribute value is not to be saved in a conventional way, separate table is used to store the complex value
*
- * {@inheritdoc}
* @since 101.0.0
*/
public function isScalar()
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml
index 963c4db8d7bfd..a7d8d59cd9043 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetSection.xml
@@ -18,6 +18,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
index 81c6a80e7e3a7..fec0a9f672990 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
@@ -14,13 +14,13 @@
-
-
-
+
+
+
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index f17ab4d65ef81..6e86420ff9de5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
@@ -15,8 +15,8 @@
-
-
+
+
@@ -33,7 +33,7 @@
-
+
diff --git a/app/code/Magento/Catalog/view/base/web/js/price-box.js b/app/code/Magento/Catalog/view/base/web/js/price-box.js
index 783d39cddbc76..de68d769885fd 100644
--- a/app/code/Magento/Catalog/view/base/web/js/price-box.js
+++ b/app/code/Magento/Catalog/view/base/web/js/price-box.js
@@ -78,7 +78,11 @@ define([
pricesCode = [],
priceValue, origin, finalPrice;
- this.cache.additionalPriceObject = this.cache.additionalPriceObject || {};
+ if (typeof newPrices !== 'undefined' && newPrices.hasOwnProperty('prices')) {
+ this.cache.additionalPriceObject = {};
+ } else {
+ this.cache.additionalPriceObject = this.cache.additionalPriceObject || {};
+ }
if (newPrices) {
$.extend(this.cache.additionalPriceObject, newPrices);
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 3cf167e9e37e7..84fa13e6599d9 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -129,6 +129,16 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
*/
const COL_NAME = 'name';
+ /**
+ * Column new_from_date.
+ */
+ const COL_NEW_FROM_DATE = 'new_from_date';
+
+ /**
+ * Column new_to_date.
+ */
+ const COL_NEW_TO_DATE = 'new_to_date';
+
/**
* Column product website.
*/
@@ -292,7 +302,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
ValidatorInterface::ERROR_MEDIA_PATH_NOT_ACCESSIBLE => 'Imported resource (image) does not exist in the local media storage',
ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE => 'Imported resource (image) could not be downloaded from external resource due to timeout or access permissions',
ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid',
- ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually'
+ ValidatorInterface::ERROR_DUPLICATE_URL_KEY => 'Url key: \'%s\' was already generated for an item with the SKU: \'%s\'. You need to specify the unique URL key manually',
+ ValidatorInterface::ERROR_NEW_TO_DATE => 'Make sure new_to_date is later than or the same as new_from_date',
];
/**
@@ -313,8 +324,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
Product::COL_TYPE => 'product_type',
Product::COL_PRODUCT_WEBSITES => 'product_websites',
'status' => 'product_online',
- 'news_from_date' => 'new_from_date',
- 'news_to_date' => 'new_to_date',
+ 'news_from_date' => self::COL_NEW_FROM_DATE,
+ 'news_to_date' => self::COL_NEW_TO_DATE,
'options_container' => 'display_product_options_in',
'minimal_price' => 'map_price',
'msrp' => 'msrp_price',
@@ -2477,6 +2488,20 @@ public function validateRow(array $rowData, $rowNum)
}
}
}
+
+ if (!empty($rowData[self::COL_NEW_FROM_DATE]) && !empty($rowData[self::COL_NEW_TO_DATE])
+ ) {
+ $newFromTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_FROM_DATE], false));
+ $newToTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_TO_DATE], false));
+ if ($newFromTimestamp > $newToTimestamp) {
+ $this->addRowError(
+ ValidatorInterface::ERROR_NEW_TO_DATE,
+ $rowNum,
+ $rowData[self::COL_NEW_TO_DATE]
+ );
+ }
+ }
+
return !$this->getErrorAggregator()->isRowInvalid($rowNum);
}
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php
index 17f7fae28ba75..49ffdebe7724d 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php
@@ -85,6 +85,8 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn
const ERROR_DUPLICATE_URL_KEY = 'duplicatedUrlKey';
+ const ERROR_NEW_TO_DATE = 'invalidNewToDateValue';
+
/**
* Value that means all entities (e.g. websites, groups etc.)
*/
diff --git a/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php b/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php
new file mode 100644
index 0000000000000..5908bde7c5a5f
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Plugin/Webapi/Controller/Rest/InputParamsResolver.php
@@ -0,0 +1,90 @@
+request = $request;
+ }
+
+ /**
+ * Add 'save_rewrites_history' param to the product data
+ *
+ * @see \Magento\CatalogUrlRewrite\Plugin\Catalog\Controller\Adminhtml\Product\Initialization\Helper
+ * @param InputParamsResolverController $subject
+ * @param array $result
+ * @return array
+ */
+ public function afterResolve(InputParamsResolverController $subject, array $result): array
+ {
+ $route = $subject->getRoute();
+ $serviceMethodName = $route->getServiceMethod();
+ $serviceClassName = $route->getServiceClass();
+ $requestBodyParams = $this->request->getBodyParams();
+
+ if ($this->isProductSaveCalled($serviceClassName, $serviceMethodName)
+ && $this->isCustomAttributesExists($requestBodyParams)) {
+ foreach ($requestBodyParams['product']['custom_attributes'] as $attribute) {
+ if ($attribute['attribute_code'] === 'save_rewrites_history') {
+ foreach ($result as $resultItem) {
+ if ($resultItem instanceof Product) {
+ $resultItem->setData('save_rewrites_history', (bool)$attribute['value']);
+ break 2;
+ }
+ }
+ break;
+ }
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Check that product save method called
+ *
+ * @param string $serviceClassName
+ * @param string $serviceMethodName
+ * @return bool
+ */
+ private function isProductSaveCalled(string $serviceClassName, string $serviceMethodName): bool
+ {
+ return $serviceClassName === ProductRepositoryInterface::class && $serviceMethodName === 'save';
+ }
+
+ /**
+ * Check is any custom options exists in product data
+ *
+ * @param array $requestBodyParams
+ * @return bool
+ */
+ private function isCustomAttributesExists(array $requestBodyParams): bool
+ {
+ return !empty($requestBodyParams['product']['custom_attributes']);
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php
new file mode 100644
index 0000000000000..9e611039e97cc
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Plugin/Webapi/Controller/Rest/InputParamsResolverTest.php
@@ -0,0 +1,120 @@
+saveRewritesHistory = 'save_rewrites_history';
+ $this->requestBodyParams = [
+ 'product' => [
+ 'sku' => 'test',
+ 'custom_attributes' => [
+ [
+ 'attribute_code' => $this->saveRewritesHistory,
+ 'value' => 1
+ ]
+ ]
+ ]
+ ];
+
+ $this->route = $this->createPartialMock(Route::class, ['getServiceMethod', 'getServiceClass']);
+ $this->request = $this->createPartialMock(RestRequest::class, ['getBodyParams']);
+ $this->request->method('getBodyParams')
+ ->willReturn($this->requestBodyParams);
+ $this->subject = $this->createPartialMock(InputParamsResolver::class, ['getRoute']);
+ $this->subject->method('getRoute')
+ ->willReturn($this->route);
+ $this->product = $this->createPartialMock(Product::class, ['setData']);
+
+ $this->result = [false, $this->product, 'test'];
+
+ $this->objectManager = new ObjectManager($this);
+ $this->plugin = $this->objectManager->getObject(
+ InputParamsResolverPlugin::class,
+ [
+ 'request' => $this->request
+ ]
+ );
+ }
+
+ public function testAfterResolve()
+ {
+ $this->route->method('getServiceClass')
+ ->willReturn(ProductRepositoryInterface::class);
+ $this->route->method('getServiceMethod')
+ ->willReturn('save');
+ $this->product->expects($this->once())
+ ->method('setData')
+ ->with($this->saveRewritesHistory, true);
+
+ $this->plugin->afterResolve($this->subject, $this->result);
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json
index 66011fcac287e..f6d3f2102940c 100644
--- a/app/code/Magento/CatalogUrlRewrite/composer.json
+++ b/app/code/Magento/CatalogUrlRewrite/composer.json
@@ -13,6 +13,9 @@
"magento/framework": "101.0.*",
"magento/module-ui": "101.0.*"
},
+ "suggest": {
+ "magento/module-webapi": "*"
+ },
"type": "magento2-module",
"version": "100.2.5",
"license": [
diff --git a/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml
index ac8beb362f0fb..34b7487725d76 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml
@@ -7,4 +7,7 @@
-->
+
+
+
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js
index d31c0dca38116..fde88ebadb393 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js
@@ -35,7 +35,7 @@ define([
var checkoutConfig = window.checkoutConfig,
validators = [],
observedElements = [],
- postcodeElement = null,
+ postcodeElements = [],
postcodeElementName = 'postcode';
validators.push(defaultValidator);
@@ -101,7 +101,7 @@ define([
if (element.index === postcodeElementName) {
this.bindHandler(element, delay);
- postcodeElement = element;
+ postcodeElements.push(element);
}
},
@@ -136,7 +136,13 @@ define([
if (!formPopUpState.isVisible()) {
clearTimeout(self.validateAddressTimeout);
self.validateAddressTimeout = setTimeout(function () {
- self.postcodeValidation();
+ if (element.index === postcodeElementName) {
+ self.postcodeValidation(element);
+ } else {
+ $.each(postcodeElements, function (index, elem) {
+ self.postcodeValidation(elem);
+ });
+ }
self.validateFields();
}, delay);
}
@@ -148,8 +154,8 @@ define([
/**
* @return {*}
*/
- postcodeValidation: function () {
- var countryId = $('select[name="country_id"]').val(),
+ postcodeValidation: function (postcodeElement) {
+ var countryId = $('select[name="country_id"]:visible').val(),
validationResult,
warnMessage;
@@ -178,8 +184,8 @@ define([
*/
validateFields: function () {
var addressFlat = addressConverter.formDataProviderToFlatData(
- this.collectObservedData(),
- 'shippingAddress'
+ this.collectObservedData(),
+ 'shippingAddress'
),
address;
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
index 6b5d08c2641cc..6f9a1a46826da 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
@@ -17,7 +17,8 @@ define([
'Magento_Customer/js/customer-data',
'Magento_Checkout/js/action/set-billing-address',
'Magento_Ui/js/model/messageList',
- 'mage/translate'
+ 'mage/translate',
+ 'Magento_Checkout/js/model/shipping-rates-validator'
],
function (
ko,
@@ -33,7 +34,8 @@ function (
customerData,
setBillingAddressAction,
globalMessageList,
- $t
+ $t,
+ shippingRatesValidator
) {
'use strict';
@@ -71,6 +73,7 @@ function (
quote.paymentMethod.subscribe(function () {
checkoutDataResolver.resolveBillingAddress();
}, this);
+ shippingRatesValidator.initFields(this.get('name') + '.form-fields');
},
/**
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 6b6c1762fadd9..78974877dd90d 100644
--- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
@@ -477,7 +477,9 @@ define([
_getPrices: function () {
var prices = {},
elements = _.toArray(this.options.settings),
- hasProductPrice = false;
+ hasProductPrice = false,
+ optionPriceDiff = 0,
+ allowedProduct, optionPrices, basePrice, optionFinalPrice;
_.each(elements, function (element) {
var selected = element.options[element.selectedIndex],
@@ -485,8 +487,23 @@ define([
priceValue = {};
if (config && config.allowedProducts.length === 1 && !hasProductPrice) {
+ prices = {};
priceValue = this._calculatePrice(config);
hasProductPrice = true;
+ } else if (element.value) {
+ allowedProduct = this._getAllowedProductWithMinPrice(config.allowedProducts);
+ optionPrices = this.options.spConfig.optionPrices;
+ basePrice = parseFloat(this.options.spConfig.prices.basePrice.amount);
+
+ if (!_.isEmpty(allowedProduct)) {
+ optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
+ optionPriceDiff = optionFinalPrice - basePrice;
+ }
+
+ if (optionPriceDiff !== 0) {
+ prices = {};
+ priceValue = this._calculatePriceDifference(allowedProduct);
+ }
}
prices[element.attributeId] = priceValue;
@@ -495,6 +512,55 @@ define([
return prices;
},
+ /**
+ * Get product with minimum price from selected options.
+ *
+ * @param {Array} allowedProducts
+ * @returns {String}
+ * @private
+ */
+ _getAllowedProductWithMinPrice: function (allowedProducts) {
+ var optionPrices = this.options.spConfig.optionPrices,
+ product = {},
+ optionMinPrice, optionFinalPrice;
+
+ _.each(allowedProducts, function (allowedProduct) {
+ optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount);
+
+ if (_.isEmpty(product)) {
+ optionMinPrice = optionFinalPrice;
+ product = allowedProduct;
+ }
+
+ if (optionFinalPrice < optionMinPrice) {
+ product = allowedProduct;
+ }
+ }, this);
+
+ return product;
+ },
+
+ /**
+ * Calculate price difference for allowed product
+ *
+ * @param {*} allowedProduct - Product
+ * @returns {*}
+ * @private
+ */
+ _calculatePriceDifference: function (allowedProduct) {
+ var displayPrices = $(this.options.priceHolderSelector).priceBox('option').prices,
+ newPrices = this.options.spConfig.optionPrices[allowedProduct];
+
+ _.each(displayPrices, function (price, code) {
+
+ if (newPrices[code]) {
+ displayPrices[code].amount = newPrices[code].amount - displayPrices[code].amount;
+ }
+ });
+
+ return displayPrices;
+ },
+
/**
* Returns prices for configured products
*
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php
index a5e31c613d17b..740b29c9676f3 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute.php
@@ -287,6 +287,12 @@ public function beforeSave()
}
}
+ if ($this->getFrontendInput() == 'media_image') {
+ if (!$this->getFrontendModel()) {
+ $this->setFrontendModel(\Magento\Catalog\Model\Product\Attribute\Frontend\Image::class);
+ }
+ }
+
if ($this->getBackendType() == 'gallery') {
if (!$this->getBackendModel()) {
$this->setBackendModel(\Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend::class);
diff --git a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php
index c7ce4b2f2f11b..131eca5f33487 100644
--- a/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php
+++ b/app/code/Magento/Newsletter/Model/ResourceModel/Subscriber.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Newsletter\Model\ResourceModel;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Framework\App\ObjectManager;
+
/**
* Newsletter subscriber resource model
*
@@ -48,6 +51,13 @@ class Subscriber extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
*/
protected $mathRandom;
+ /**
+ * Store manager
+ *
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* Construct
*
@@ -55,15 +65,19 @@ class Subscriber extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
* @param \Magento\Framework\Stdlib\DateTime\DateTime $date
* @param \Magento\Framework\Math\Random $mathRandom
* @param string $connectionName
+ * @param StoreManagerInterface $storeManager
*/
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
\Magento\Framework\Stdlib\DateTime\DateTime $date,
\Magento\Framework\Math\Random $mathRandom,
- $connectionName = null
+ $connectionName = null,
+ StoreManagerInterface $storeManager = null
) {
$this->_date = $date;
$this->mathRandom = $mathRandom;
+ $this->storeManager = $storeManager ?: ObjectManager::getInstance()
+ ->get(StoreManagerInterface::class);
parent::__construct($context, $connectionName);
}
@@ -118,6 +132,9 @@ public function loadByEmail($subscriberEmail)
*/
public function loadByCustomerData(\Magento\Customer\Api\Data\CustomerInterface $customer)
{
+ $storeId = (int)$customer->getStoreId() ?: $this->storeManager
+ ->getWebsite($customer->getWebsiteId())->getDefaultStore()->getId();
+
$select = $this->connection
->select()
->from($this->getMainTable())
@@ -128,7 +145,7 @@ public function loadByCustomerData(\Magento\Customer\Api\Data\CustomerInterface
$select,
[
'customer_id' => $customer->getId(),
- 'store_id' => $customer->getStoreId()
+ 'store_id' => $storeId
]
);
@@ -146,7 +163,7 @@ public function loadByCustomerData(\Magento\Customer\Api\Data\CustomerInterface
$select,
[
'subscriber_email' => $customer->getEmail(),
- 'store_id' => $customer->getStoreId()
+ 'store_id' => $storeId
]
);
diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php
index 5e0d5448f11ca..a1d929d8db7bf 100644
--- a/app/code/Magento/Newsletter/Model/Subscriber.php
+++ b/app/code/Magento/Newsletter/Model/Subscriber.php
@@ -612,16 +612,17 @@ protected function _updateCustomerSubscription($customerId, $subscribe)
$this->setStatus($status);
+ $storeId = $customerData->getStoreId();
+ if ((int)$customerData->getStoreId() === 0) {
+ $storeId = $this->_storeManager->getWebsite($customerData->getWebsiteId())->getDefaultStore()->getId();
+ }
+
if (!$this->getId()) {
- $storeId = $customerData->getStoreId();
- if ($customerData->getStoreId() == 0) {
- $storeId = $this->_storeManager->getWebsite($customerData->getWebsiteId())->getDefaultStore()->getId();
- }
$this->setStoreId($storeId)
->setCustomerId($customerData->getId())
->setEmail($customerData->getEmail());
} else {
- $this->setStoreId($customerData->getStoreId())
+ $this->setStoreId($storeId)
->setEmail($customerData->getEmail());
}
diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/ProblemTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/ProblemTest.php
index 889fc11d71d7e..1de3e6096cd96 100644
--- a/app/code/Magento/Newsletter/Test/Unit/Model/ProblemTest.php
+++ b/app/code/Magento/Newsletter/Test/Unit/Model/ProblemTest.php
@@ -68,6 +68,7 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
$this->subscriberFactoryMock = $this->getMockBuilder(SubscriberFactory::class)
+ ->disableOriginalConstructor()
->getMock();
$this->subscriberMock = $this->getMockBuilder(Subscriber::class)
->disableOriginalConstructor()
diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php
index 99b23de5b6b6c..9c543c831ded3 100644
--- a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php
+++ b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php
@@ -211,6 +211,7 @@ public function testSubscribeNotLoggedIn()
public function testUpdateSubscription()
{
+ $storeId = 2;
$customerId = 1;
$customerDataMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)
->getMock();
@@ -232,7 +233,7 @@ public function testUpdateSubscription()
->method('getConfirmationStatus')
->with($customerId)
->willReturn('account_confirmation_required');
- $customerDataMock->expects($this->once())->method('getStoreId')->willReturn('store_id');
+ $customerDataMock->expects($this->exactly(2))->method('getStoreId')->willReturn($storeId);
$customerDataMock->expects($this->once())->method('getEmail')->willReturn('email');
$storeModel = $this->getMockBuilder(\Magento\Store\Model\Store::class)
@@ -246,6 +247,7 @@ public function testUpdateSubscription()
public function testUnsubscribeCustomerById()
{
+ $storeId = 2;
$customerId = 1;
$customerDataMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)
->getMock();
@@ -263,7 +265,7 @@ public function testUnsubscribeCustomerById()
);
$customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id');
$this->resource->expects($this->atLeastOnce())->method('save')->willReturnSelf();
- $customerDataMock->expects($this->once())->method('getStoreId')->willReturn('store_id');
+ $customerDataMock->expects($this->exactly(2))->method('getStoreId')->willReturn($storeId);
$customerDataMock->expects($this->once())->method('getEmail')->willReturn('email');
$this->sendEmailCheck();
@@ -272,6 +274,7 @@ public function testUnsubscribeCustomerById()
public function testSubscribeCustomerById()
{
+ $storeId = 2;
$customerId = 1;
$customerDataMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)
->getMock();
@@ -289,7 +292,7 @@ public function testSubscribeCustomerById()
);
$customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id');
$this->resource->expects($this->atLeastOnce())->method('save')->willReturnSelf();
- $customerDataMock->expects($this->once())->method('getStoreId')->willReturn('store_id');
+ $customerDataMock->expects($this->exactly(2))->method('getStoreId')->willReturn($storeId);
$customerDataMock->expects($this->once())->method('getEmail')->willReturn('email');
$this->sendEmailCheck();
@@ -298,6 +301,7 @@ public function testSubscribeCustomerById()
public function testSubscribeCustomerById1()
{
+ $storeId = 2;
$customerId = 1;
$customerDataMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)
->getMock();
@@ -315,7 +319,7 @@ public function testSubscribeCustomerById1()
);
$customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id');
$this->resource->expects($this->atLeastOnce())->method('save')->willReturnSelf();
- $customerDataMock->expects($this->once())->method('getStoreId')->willReturn('store_id');
+ $customerDataMock->expects($this->exactly(2))->method('getStoreId')->willReturn($storeId);
$customerDataMock->expects($this->once())->method('getEmail')->willReturn('email');
$this->sendEmailCheck();
$this->customerAccountManagement->expects($this->once())
@@ -329,6 +333,7 @@ public function testSubscribeCustomerById1()
public function testSubscribeCustomerByIdAfterConfirmation()
{
+ $storeId = 2;
$customerId = 1;
$customerDataMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)
->getMock();
@@ -346,7 +351,7 @@ public function testSubscribeCustomerByIdAfterConfirmation()
);
$customerDataMock->expects($this->atLeastOnce())->method('getId')->willReturn('id');
$this->resource->expects($this->atLeastOnce())->method('save')->willReturnSelf();
- $customerDataMock->expects($this->once())->method('getStoreId')->willReturn('store_id');
+ $customerDataMock->expects($this->exactly(2))->method('getStoreId')->willReturn($storeId);
$customerDataMock->expects($this->once())->method('getEmail')->willReturn('email');
$this->sendEmailCheck();
$this->customerAccountManagement->expects($this->never())->method('getConfirmationStatus');
diff --git a/app/code/Magento/Review/Model/Review.php b/app/code/Magento/Review/Model/Review.php
index c00af3fc61407..e689d4ed460ac 100644
--- a/app/code/Magento/Review/Model/Review.php
+++ b/app/code/Magento/Review/Model/Review.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Review\Model;
+use Magento\Framework\DataObject;
use Magento\Catalog\Model\Product;
use Magento\Framework\DataObject\IdentityInterface;
use Magento\Review\Model\ResourceModel\Review\Product\Collection as ProductCollection;
@@ -327,6 +328,9 @@ public function appendSummary($collection)
$item->setRatingSummary($summary);
}
}
+ if (!$item->getRatingSummary()) {
+ $item->setRatingSummary(new DataObject());
+ }
}
return $this;
diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php
index a1987f67e47f2..dcf742ee91103 100644
--- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php
+++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php
@@ -615,6 +615,9 @@ public function getValueElement()
// date format intentionally hard-coded
$elementParams['input_format'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT;
$elementParams['date_format'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT;
+ $elementParams['placeholder'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT;
+ $elementParams['autocomplete'] = 'off';
+ $elementParams['readonly'] = 'true';
}
return $this->getForm()->addField(
$this->getPrefix() . '__' . $this->getId() . '__value',
diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js
index b094b9818364a..8e36562ebd7fe 100644
--- a/app/code/Magento/Rule/view/adminhtml/web/rules.js
+++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js
@@ -220,6 +220,8 @@ define([
var elem = Element.down(elemContainer, 'input.input-text');
+ jQuery(elem).trigger('contentUpdated');
+
if (elem) {
elem.focus();
diff --git a/app/code/Magento/Sales/Setup/UpgradeSchema.php b/app/code/Magento/Sales/Setup/UpgradeSchema.php
index fe2d422914c57..6e625a1eaaa19 100644
--- a/app/code/Magento/Sales/Setup/UpgradeSchema.php
+++ b/app/code/Magento/Sales/Setup/UpgradeSchema.php
@@ -109,6 +109,9 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con
if (version_compare($context->getVersion(), '2.0.10', '<')) {
$this->expandRemoteIpField($installer);
}
+ if (version_compare($context->getVersion(), '2.0.11', '<')) {
+ $this->expandLastTransIdField($installer);
+ }
}
/**
@@ -161,4 +164,21 @@ private function expandRemoteIpField(SchemaSetupInterface $installer)
]
);
}
+
+ /**
+ * @param SchemaSetupInterface $installer
+ * @return void
+ */
+ private function expandLastTransIdField(SchemaSetupInterface $installer)
+ {
+ $connection = $installer->getConnection(self::$connectionName);
+ $connection->modifyColumn(
+ $installer->getTable('sales_order_payment', self::$connectionName),
+ 'last_trans_id',
+ [
+ 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
+ 'length' => 255
+ ]
+ );
+ }
}
diff --git a/app/code/Magento/Sales/etc/module.xml b/app/code/Magento/Sales/etc/module.xml
index 3a82dd64a7949..0fd2124785ba7 100644
--- a/app/code/Magento/Sales/etc/module.xml
+++ b/app/code/Magento/Sales/etc/module.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
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 e43d32760febb..dc179b6ee4ac1 100644
--- a/app/code/Magento/Sales/view/frontend/templates/order/items.phtml
+++ b/app/code/Magento/Sales/view/frontend/templates/order/items.phtml
@@ -29,9 +29,10 @@
getItems(); ?>
+
getParentItem()) continue; ?>
-
+
= $block->getItemHtml($item) ?>
helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $item) && $item->getGiftMessageId()): ?>
helper('Magento\GiftMessage\Helper\Message')->getGiftMessageForEntity($item); ?>
@@ -62,8 +63,8 @@
-
+
isPagerDisplayed()): ?>
diff --git a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
index 5ecf1ebe893bc..9b3633fde60b4 100644
--- a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
+++ b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
@@ -26,14 +26,18 @@