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; ?> - + 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 @@
  1. -
    +
    @@ -46,8 +50,8 @@
-
+
diff --git a/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js b/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js index f393cc3fcd3bc..17e61a77d98a3 100644 --- a/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js +++ b/app/code/Magento/Sales/view/frontend/web/js/view/last-ordered-items.js @@ -5,27 +5,43 @@ define([ 'uiComponent', - 'Magento_Customer/js/customer-data' -], function (Component, customerData) { + 'Magento_Customer/js/customer-data', + 'underscore' +], function (Component, customerData, _) { 'use strict'; return Component.extend({ + defaults: { + isShowAddToCart: false + }, + /** @inheritdoc */ initialize: function () { - var isShowAddToCart = false, - item; - this._super(); this.lastOrderedItems = customerData.get('last-ordered-items'); + this.lastOrderedItems.subscribe(this.checkSalableItems.bind(this)); + this.checkSalableItems(); + + return this; + }, + + /** @inheritdoc */ + initObservable: function () { + this._super() + .observe('isShowAddToCart'); + + return this; + }, - for (item in this.lastOrderedItems.items) { - if (item['is_saleable']) { - isShowAddToCart = true; - break; - } - } + /** + * Check if items is_saleable and change add to cart button visibility. + */ + checkSalableItems: function () { + var isShowAddToCart = _.some(this.lastOrderedItems().items, { + 'is_saleable': true + }); - this.lastOrderedItems.isShowAddToCart = isShowAddToCart; + this.isShowAddToCart(isShowAddToCart); } }); }); diff --git a/app/code/Magento/Swatches/Helper/Media.php b/app/code/Magento/Swatches/Helper/Media.php index eb932f723c154..5019b9928ab1e 100644 --- a/app/code/Magento/Swatches/Helper/Media.php +++ b/app/code/Magento/Swatches/Helper/Media.php @@ -11,6 +11,7 @@ /** * Helper to move images from tmp to catalog directory + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 @@ -19,7 +20,6 @@ class Media extends \Magento\Framework\App\Helper\AbstractHelper { /** * Swatch area inside media folder - * */ const SWATCH_MEDIA_PATH = 'attribute/swatch'; @@ -100,6 +100,8 @@ public function __construct( } /** + * Get swatch attribute image + * * @param string $swatchType * @param string $file * @return string @@ -110,13 +112,17 @@ public function getSwatchAttributeImage($swatchType, $file) $absoluteImagePath = $this->mediaDirectory ->getAbsolutePath($this->getSwatchMediaPath() . '/' . $generationPath); if (!file_exists($absoluteImagePath)) { - $this->generateSwatchVariations($file); + try { + $this->generateSwatchVariations($file); + } catch (\Exception $e) { + return ''; + } } return $this->getSwatchMediaUrl() . '/' . $generationPath; } /** - * move image from tmp to catalog dir + * Move image from tmp to catalog dir * * @param string $file * @return string path @@ -152,7 +158,7 @@ public function moveImageFromTmp($file) /** * Check whether file to move exists. Getting unique name * - * @param $file + * @param string $file * @return string */ protected function getUniqueFileName($file) @@ -176,6 +182,7 @@ protected function getUniqueFileName($file) * * @param string $imageUrl * @return $this + * @throws \Exception */ public function generateSwatchVariations($imageUrl) { @@ -234,7 +241,7 @@ protected function generateNamePath($imageConfig, $imageUrl, $swatchType) * Generate folder name WIDTHxHEIGHT based on config in view.xml * * @param string $swatchType - * @param null $imageConfig + * @param array|null $imageConfig * @return string */ public function getFolderNameSize($swatchType, $imageConfig = null) @@ -336,6 +343,8 @@ protected function prepareFile($file) } /** + * Get registered themes + * * @return \Magento\Theme\Model\ResourceModel\Theme\Collection */ private function getRegisteredThemes() diff --git a/app/code/Magento/Swatches/Test/Unit/Helper/MediaTest.php b/app/code/Magento/Swatches/Test/Unit/Helper/MediaTest.php index aed9c1da41289..aebfbc2cbf0d4 100644 --- a/app/code/Magento/Swatches/Test/Unit/Helper/MediaTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Helper/MediaTest.php @@ -91,6 +91,8 @@ protected function setUp() /** * @dataProvider dataForFullPath + * @param string $swatchType + * @param string $expectedResult */ public function testGetSwatchAttributeImage($swatchType, $expectedResult) { @@ -98,7 +100,6 @@ public function testGetSwatchAttributeImage($swatchType, $expectedResult) ->expects($this->once()) ->method('getStore') ->willReturn($this->storeMock); - $this->storeMock ->expects($this->once()) ->method('getBaseUrl') @@ -107,11 +108,41 @@ public function testGetSwatchAttributeImage($swatchType, $expectedResult) $this->generateImageConfig(); - $this->testGenerateSwatchVariations(); + $image = $this->createPartialMock(\Magento\Framework\Image::class, [ + 'resize', + 'save', + 'keepTransparency', + 'constrainOnly', + 'keepFrame', + 'keepAspectRatio', + 'backgroundColor', + 'quality' + ]); + $this->imageFactoryMock->expects($this->atLeastOnce()) + ->method('create') + ->willReturn($image); + $image->expects($this->atLeastOnce()) + ->method('resize') + ->will($this->returnSelf()); + $image->expects($this->atLeastOnce()) + ->method('backgroundColor') + ->with([255, 255, 255]) + ->willReturnSelf(); $result = $this->mediaHelperObject->getSwatchAttributeImage($swatchType, '/f/i/file.png'); + $this->assertEquals($expectedResult, $result); + } + + public function testGetSwatchAttributeImageWithMissingFile() + { + $this->generateImageConfig(); + + $this->imageFactoryMock->expects($this->atLeastOnce()) + ->method('create') + ->willThrowException(new \Exception('')); - $this->assertEquals($result, $expectedResult); + $result = $this->mediaHelperObject->getSwatchAttributeImage('swatch_image', '/f/i/file.png'); + $this->assertEquals('', $result); } /** @@ -195,6 +226,9 @@ public function testGetSwatchMediaUrl() /** * @dataProvider dataForFolderName + * @param string $swatchType + * @param array|null $imageConfig + * @param string $expectedResult */ public function testGetFolderNameSize($swatchType, $imageConfig, $expectedResult) { @@ -293,6 +327,8 @@ public function testGetSwatchMediaPath() /** * @dataProvider getSwatchTypes + * @param string $swatchType + * @param string $expectedResult */ public function testGetSwatchCachePath($swatchType, $expectedResult) { 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 1aabab8b1d5d6..5e4c0a2c84f14 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 @@ -974,13 +974,29 @@ define([ * @private */ _getPrices: function (newPrices, displayPrices) { - var $widget = this; + var $widget = this, + optionPriceDiff = 0, + allowedProduct, optionPrices, basePrice, optionFinalPrice; if (_.isEmpty(newPrices)) { - newPrices = $widget.options.jsonConfig.prices; + 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; + } } _.each(displayPrices, function (price, code) { + if (newPrices[code]) { displayPrices[code].amount = newPrices[code].amount - displayPrices[code].amount; } @@ -989,6 +1005,34 @@ define([ return displayPrices; }, + /** + * Get product with minimum price from selected options. + * + * @param {Array} allowedProducts + * @returns {String} + * @private + */ + _getAllowedProductWithMinPrice: function (allowedProducts) { + var optionPrices = this.options.jsonConfig.optionPrices, + product = {}, + optionFinalPrice, optionMinPrice; + + _.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; + }, + /** * Gets all product media and change current to the needed one * diff --git a/app/code/Magento/Ui/view/base/web/templates/form/field.html b/app/code/Magento/Ui/view/base/web/templates/form/field.html index 144825967401e..ed84e158819a2 100644 --- a/app/code/Magento/Ui/view/base/web/templates/form/field.html +++ b/app/code/Magento/Ui/view/base/web/templates/form/field.html @@ -8,9 +8,11 @@ visible="visible" css="$data.additionalClasses" attr="'data-index': index"> - - +
+ +
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 c9c97930297cb..40ebb6f3c4569 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,9 +97,11 @@ display: inline-block; font-size: @notifications__font-size; font-weight: @font-weight__bold; + height: 18px; left: 50%; margin-left: .3em; margin-top: -1.1em; + min-width: 18px; padding: .3em .5em; position: absolute; top: 50%; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less index 01b5ae080130f..24779dc8baa30 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less @@ -54,6 +54,8 @@ &._required { > .admin__field-label { span { + padding-left: 1.5rem; + &:after { left: 0; margin-left: @temp_gutter; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index 8107c81030590..f195a28908abe 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -221,7 +221,7 @@ overflow: hidden; } - label { + span { display: inline-block; line-height: @field-label__line-height; vertical-align: middle; @@ -239,7 +239,7 @@ .required > &, // ToDo UI: change classes 'required' to '_required'. ._required > & { - > label { + span { &:after { color: @validation__color; content: '*'; @@ -526,7 +526,7 @@ position: absolute; top: 0; - label { + span { &:before { display: block; } @@ -541,7 +541,7 @@ } & > .admin__field-label { - label { + span { &:before { display: none; } 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 c94dd4e62ada8..6d46145df388c 100644 --- a/app/design/adminhtml/Magento/backend/web/css/styles-old.less +++ b/app/design/adminhtml/Magento/backend/web/css/styles-old.less @@ -3845,6 +3845,26 @@ .rule-param-edit .element { display: inline; + position: relative; + } + + .rule-param-edit .element input.input-date, + .rule-param-edit .element input.input-date[readonly] { + background-color: @color-white; + min-width: 140px; + width: 140px !important; + cursor: pointer; + text-align: center; + opacity: 1; + margin-right: 10px; + padding-right: 40px; + + + .ui-datepicker-trigger { + position: absolute; + width: 140px; + text-align: right; + left: 0; + } } .rule-param-edit .element .addafter { diff --git a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less index a0f734b05cbd1..1e4a92fa0701f 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less @@ -612,10 +612,6 @@ padding: 25px; .col { - &.name { - padding-left: 0; - } - &.price { text-align: center; } 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 8d1ce2b2f896e..d923bee0b7f8a 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_extends.less +++ b/app/design/frontend/Magento/luma/web/css/source/_extends.less @@ -1506,6 +1506,10 @@ position: relative; z-index: 1; } + .limiter { + display: inline-block; + float: right; + } .toolbar-amount { .lib-css(line-height, @pager__line-height); diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php index d1fd351302414..6dbf2b1aa6a12 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php @@ -6,9 +6,9 @@ namespace Magento\Mtf\Client\Element; -use Magento\Mtf\ObjectManager; -use Magento\Mtf\Client\Locator; use Magento\Mtf\Client\ElementInterface; +use Magento\Mtf\Client\Locator; +use Magento\Mtf\ObjectManager; /** * Typified element class for conditions. @@ -135,6 +135,13 @@ class ConditionsElement extends SimpleElement */ protected $chooserGridLocator = 'div[id*=chooser]'; + /** + * Datepicker xpath. + * + * @var string + */ + private $datepicker = './/*[contains(@class,"ui-datepicker-trigger")]'; + /** * Key of last find param. * @@ -189,10 +196,7 @@ class ConditionsElement extends SimpleElement protected $exception; /** - * Set value to conditions. - * - * @param string $value - * @return void + * @inheritdoc */ public function setValue($value) { @@ -411,7 +415,16 @@ protected function fillText($rule, ElementInterface $param) { $value = $param->find('input', Locator::SELECTOR_TAG_NAME); if ($value->isVisible()) { - $value->setValue($rule); + if (!$value->getAttribute('readonly')) { + $value->setValue($rule); + } else { + $datepicker = $param->find( + $this->datepicker, + Locator::SELECTOR_XPATH, + DatepickerElement::class + ); + $datepicker->setValue($rule); + } $apply = $param->find('.//*[@class="rule-param-apply"]', Locator::SELECTOR_XPATH); if ($apply->isVisible()) { diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php index a0e350cb3da43..eb277c2cc43dd 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php @@ -66,13 +66,16 @@ public function setValue($value) $date = $this->parseDate($value); $date[1] = ltrim($date[1], '0'); $this->click(); - $this->find($this->datePickerButton, Locator::SELECTOR_XPATH)->click(); $datapicker = $this->find($this->datePickerBlock, Locator::SELECTOR_XPATH); + $datepickerClose = $datapicker->find($this->datePickerButtonClose, Locator::SELECTOR_XPATH); + if (!$datepickerClose->isVisible()) { + $this->find($this->datePickerButton, Locator::SELECTOR_XPATH)->click(); + } $datapicker->find($this->datePickerYear, Locator::SELECTOR_XPATH, 'select')->setValue($date[2]); $datapicker->find($this->datePickerMonth, Locator::SELECTOR_XPATH, 'select')->setValue($date[0]); $datapicker->find(sprintf($this->datePickerCalendar, $date[1]), Locator::SELECTOR_XPATH)->click(); if ($datapicker->isVisible()) { - $datapicker->find($this->datePickerButtonClose, Locator::SELECTOR_XPATH)->click(); + $datepickerClose->click(); } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml index f318287720c38..87618777d7907 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/NewCategoryIds.xml @@ -11,7 +11,7 @@ [name="name"] - div[data-index="parent"]>div + div[data-index="parent"]>div[class="admin__field-control"] Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Section\ProductDetails\CategoryIds diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php index 61142adc8f9b3..fa2b66d3e9698 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/ProductForm.php @@ -28,7 +28,7 @@ class ProductForm extends FormSections * * @var string */ - protected $attribute = './/*[contains(@class,"label")]/label[text()="%s"]'; + protected $attribute = './/*[contains(@class,"label")]//span[text()="%s"]'; /** * Product new from date field on the product form diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php index 6d22d783b01bb..6382069308622 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php @@ -198,4 +198,52 @@ public function testFilterByStoreId() $this->assertGreaterThanOrEqual(1, $count); } + + /** + * Test save product with gallery image + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_image.php + * + * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws \Magento\Framework\Exception\InputException + * @throws \Magento\Framework\Exception\StateException + */ + public function testSaveProductWithGalleryImage() + { + /** @var $mediaConfig \Magento\Catalog\Model\Product\Media\Config */ + $mediaConfig = Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Model\Product\Media\Config::class); + + /** @var $mediaDirectory \Magento\Framework\Filesystem\Directory\WriteInterface */ + $mediaDirectory = Bootstrap::getObjectManager() + ->get(\Magento\Framework\Filesystem::class) + ->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA); + + $product = Bootstrap::getObjectManager()->create(\Magento\Catalog\Model\Product::class); + $product->load(1); + + $path = $mediaConfig->getBaseMediaPath() . '/magento_image.jpg'; + $absolutePath = $mediaDirectory->getAbsolutePath() . $path; + $product->addImageToMediaGallery($absolutePath, [ + 'image', + 'small_image', + ], false, false); + + /** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ + $productRepository = Bootstrap::getObjectManager() + ->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + $productRepository->save($product); + + $gallery = $product->getData('media_gallery'); + $this->assertArrayHasKey('images', $gallery); + $images = array_values($gallery['images']); + + $this->assertNotEmpty($gallery); + $this->assertTrue(isset($images[0]['file'])); + $this->assertStringStartsWith('/m/a/magento_image', $images[0]['file']); + $this->assertArrayHasKey('media_type', $images[0]); + $this->assertEquals('image', $images[0]['media_type']); + $this->assertStringStartsWith('/m/a/magento_image', $product->getData('image')); + $this->assertStringStartsWith('/m/a/magento_image', $product->getData('small_image')); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_image.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_image.php new file mode 100644 index 0000000000000..252f99c97b787 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_image.php @@ -0,0 +1,45 @@ +reinitialize(); + +/** @var \Magento\TestFramework\ObjectManager $objectManager */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @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) + ->setId(1) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice(10) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED); + +/** @var $mediaConfig \Magento\Catalog\Model\Product\Media\Config */ +$mediaConfig = $objectManager->get(\Magento\Catalog\Model\Product\Media\Config::class); + +/** @var $mediaDirectory \Magento\Framework\Filesystem\Directory\WriteInterface */ +$mediaDirectory = $objectManager->get(\Magento\Framework\Filesystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); + +$targetDirPath = $mediaConfig->getBaseMediaPath(); +$targetTmpDirPath = $mediaConfig->getBaseTmpMediaPath(); + +$mediaDirectory->create($targetDirPath); +$mediaDirectory->create($targetTmpDirPath); + +$dist = $mediaDirectory->getAbsolutePath($mediaConfig->getBaseMediaPath() . DIRECTORY_SEPARATOR . 'magento_image.jpg'); +copy(__DIR__ . '/magento_image.jpg', $dist); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$productRepository->save($product); diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php index 39db400d2d637..dbc666e49b6cf 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/Plugin/PluginTest.php @@ -167,4 +167,42 @@ private function verifySubscriptionNotExist($email) $this->assertEquals(0, (int)$subscriber->getId()); return $subscriber; } + + /** + * @magentoAppArea adminhtml + * @magentoDbIsolation enabled + */ + public function testCustomerWithZeroStoreIdIsSubscribed() + { + $objectManager = Bootstrap::getObjectManager(); + + $currentStore = $objectManager->get( + \Magento\Store\Model\StoreManagerInterface::class + )->getStore()->getId(); + + $subscriber = $objectManager->create(\Magento\Newsletter\Model\Subscriber::class); + /** @var \Magento\Newsletter\Model\Subscriber $subscriber */ + $subscriber->setStoreId($currentStore) + ->setCustomerId(0) + ->setSubscriberEmail('customer@example.com') + ->setSubscriberStatus(\Magento\Newsletter\Model\Subscriber::STATUS_SUBSCRIBED) + ->save(); + + /** @var \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerFactory */ + $customerFactory = $objectManager->get(\Magento\Customer\Api\Data\CustomerInterfaceFactory::class); + $customerDataObject = $customerFactory->create() + ->setFirstname('Firstname') + ->setLastname('Lastname') + ->setStoreId(0) + ->setEmail('customer@example.com'); + /** @var \Magento\Customer\Api\Data\CustomerInterface $customer */ + $customer = $this->accountManagement->createAccount($customerDataObject); + + $this->customerRepository->save($customer); + + $subscriber->loadByEmail('customer@example.com'); + + $this->assertEquals($customer->getId(), (int)$subscriber->getCustomerId()); + $this->assertEquals($currentStore, (int)$subscriber->getStoreId()); + } } diff --git a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php index a8451e43ade20..ac9cdd3822ec5 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php @@ -238,6 +238,7 @@ public function getHtmlAttributes() 'onchange', 'disabled', 'readonly', + 'autocomplete', 'tabindex', 'placeholder', 'data-form-part', diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Date.php b/lib/internal/Magento/Framework/Data/Form/Element/Date.php index ed5457edc7f3f..1920115ff0a3a 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Date.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Date.php @@ -146,7 +146,7 @@ public function getValueInstance() */ public function getElementHtml() { - $this->addClass('admin__control-text input-text'); + $this->addClass('admin__control-text input-text input-date'); $dateFormat = $this->getDateFormat() ?: $this->getFormat(); $timeFormat = $this->getTimeFormat(); if (empty($dateFormat)) { diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php index e29b1dcf441e4..a85c1f4aa450c 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php @@ -195,6 +195,7 @@ public function testGetHtmlAttributes() 'onchange', 'disabled', 'readonly', + 'autocomplete', 'tabindex', 'placeholder', 'data-form-part', diff --git a/lib/internal/Magento/Framework/Filesystem/Driver/Http.php b/lib/internal/Magento/Framework/Filesystem/Driver/Http.php index 09d5372291a0a..5c7fdb0630186 100644 --- a/lib/internal/Magento/Framework/Filesystem/Driver/Http.php +++ b/lib/internal/Magento/Framework/Filesystem/Driver/Http.php @@ -35,6 +35,11 @@ public function isExists($path) $status = $headers[0]; + /* Handling 302 redirection */ + if (strpos($status, '302 Found') !== false && isset($headers[1])) { + $status = $headers[1]; + } + if (strpos($status, '200 OK') === false) { $result = false; } else { diff --git a/lib/internal/Magento/Framework/Translate.php b/lib/internal/Magento/Framework/Translate.php index fd9237e0ef223..82296d776a73d 100644 --- a/lib/internal/Magento/Framework/Translate.php +++ b/lib/internal/Magento/Framework/Translate.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework; @@ -327,16 +328,21 @@ protected function _addData($data) } /** - * Load current theme translation + * Load current theme translation according to fallback * * @return $this */ protected function _loadThemeTranslation() { - $file = $this->_getThemeTranslationFile($this->getLocale()); - if ($file) { - $this->_addData($this->_getFileData($file)); + $themeFiles = $this->getThemeTranslationFilesList($this->getLocale()); + + /** @var string $file */ + foreach ($themeFiles as $file) { + if ($file) { + $this->_addData($this->_getFileData($file)); + } } + return $this; } @@ -377,11 +383,74 @@ protected function _getModuleTranslationFile($moduleName, $locale) return $file; } + /** + * Get theme translation locale file name + * + * @param string|null $locale + * @param array $config + * @return string|null + */ + private function getThemeTranslationFileName($locale, array $config) + { + $fileName = $this->_viewFileSystem->getLocaleFileName( + 'i18n' . '/' . $locale . '.csv', + $config + ); + + return $fileName ? $fileName : null; + } + + /** + * Get parent themes for the current theme in fallback order + * + * @return array + */ + private function getParentThemesList(): array + { + $themes = []; + + $parentTheme = $this->_viewDesign->getDesignTheme()->getParentTheme(); + while ($parentTheme) { + $themes[] = $parentTheme; + $parentTheme = $parentTheme->getParentTheme(); + } + $themes = array_reverse($themes); + + return $themes; + } + + /** + * Retrieve translation files for themes according to fallback + * + * @param string $locale + * + * @return array + */ + private function getThemeTranslationFilesList($locale): array + { + $translationFiles = []; + + /** @var \Magento\Framework\View\Design\ThemeInterface $theme */ + foreach ($this->getParentThemesList() as $theme) { + $config = $this->_config; + $config['theme'] = $theme->getCode(); + $translationFiles[] = $this->getThemeTranslationFileName($locale, $config); + } + + $translationFiles[] = $this->getThemeTranslationFileName($locale, $this->_config); + + return $translationFiles; + } + /** * Retrieve translation file for theme * * @param string $locale * @return string + * + * @deprecated + * + * @see \Magento\Framework\Translate::getThemeTranslationFilesList */ protected function _getThemeTranslationFile($locale) { diff --git a/lib/internal/Magento/Framework/View/Layout/Generator/Block.php b/lib/internal/Magento/Framework/View/Layout/Generator/Block.php index 063d2cb14a595..719b7871c64ff 100755 --- a/lib/internal/Magento/Framework/View/Layout/Generator/Block.php +++ b/lib/internal/Magento/Framework/View/Layout/Generator/Block.php @@ -6,6 +6,7 @@ namespace Magento\Framework\View\Layout\Generator; use Magento\Framework\App\State; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\ObjectManager\Config\Reader\Dom; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Layout; @@ -272,7 +273,7 @@ protected function getBlockInstance($block, array $arguments = []) } } if (!$block instanceof \Magento\Framework\View\Element\AbstractBlock) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( new \Magento\Framework\Phrase( 'Invalid block type: %1', [is_object($block) ? get_class($block) : (string) $block] diff --git a/lib/web/css/source/components/_modals.less b/lib/web/css/source/components/_modals.less index 51ba451681df5..bd7cf728baa12 100644 --- a/lib/web/css/source/components/_modals.less +++ b/lib/web/css/source/components/_modals.less @@ -103,6 +103,10 @@ &.confirm { .modal-inner-wrap { .lib-css(width, @modal-popup-confirm__width); + + .modal-content { + padding-right: 7rem; + } } } diff --git a/lib/web/mage/calendar.js b/lib/web/mage/calendar.js index ac154b333801d..a9ccf2cf787f9 100644 --- a/lib/web/mage/calendar.js +++ b/lib/web/mage/calendar.js @@ -66,6 +66,9 @@ * Widget calendar */ $.widget('mage.calendar', { + options: { + autoComplete: true + }, /** * Merge global options with options passed to widget invoke @@ -379,6 +382,9 @@ .addClass('v-middle') .text('') // Remove jQuery UI datepicker generated image .append('' + pickerButtonText + ''); + + $(element).attr('autocomplete', this.options.autoComplete ? 'on' : 'off'); + this._setCurrentDate(element); }, diff --git a/lib/web/mage/tabs.js b/lib/web/mage/tabs.js index e7e04a26b29c1..b441477ab8d8a 100644 --- a/lib/web/mage/tabs.js +++ b/lib/web/mage/tabs.js @@ -83,7 +83,7 @@ define([ /** * When the widget gets instantiated, the first tab that is not disabled receive focusable property - * Updated: for accessibility all tabs receive tabIndex 0 + * All tabs receive tabIndex 0 * @private */ _processTabIndex: function () { @@ -91,17 +91,8 @@ define([ self.triggers.attr('tabIndex', 0); $.each(this.collapsibles, function (i) { - if (!$(this).collapsible('option', 'disabled')) { - self.triggers.eq(i).attr('tabIndex', 0); - - return false; - } - }); - $.each(this.collapsibles, function (i) { - $(this).on('beforeOpen', function () { - self.triggers.attr('tabIndex', 0); - self.triggers.eq(i).attr('tabIndex', 0); - }); + self.triggers.attr('tabIndex', 0); + self.triggers.eq(i).attr('tabIndex', 0); }); },