diff --git a/.gitignore b/.gitignore index e05169bb7c4a..bae558e0e5b9 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ atlassian* !/vendor/.htaccess /generated/* !/generated/.htaccess +.DS_Store diff --git a/.travis.yml b/.travis.yml index e477e56500b7..649762d8e9f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,11 +22,12 @@ env: - MAGENTO_HOST_NAME="magento2.travis" matrix: - TEST_SUITE=unit + - TEST_SUITE=static + - TEST_SUITE=js GRUNT_COMMAND=spec + - TEST_SUITE=js GRUNT_COMMAND=static - TEST_SUITE=integration INTEGRATION_INDEX=1 - TEST_SUITE=integration INTEGRATION_INDEX=2 - TEST_SUITE=integration INTEGRATION_INDEX=3 - - TEST_SUITE=static - - TEST_SUITE=js - TEST_SUITE=functional ACCEPTANCE_INDEX=1 - TEST_SUITE=functional ACCEPTANCE_INDEX=2 matrix: @@ -34,7 +35,9 @@ matrix: - php: 7.0 env: TEST_SUITE=static - php: 7.0 - env: TEST_SUITE=js + env: TEST_SUITE=js GRUNT_COMMAND=spec + - php: 7.0 + env: TEST_SUITE=js GRUNT_COMMAND=static - php: 7.0 env: TEST_SUITE=functional ACCEPTANCE_INDEX=1 - php: 7.0 @@ -49,4 +52,11 @@ cache: before_install: ./dev/travis/before_install.sh install: composer install --no-interaction --prefer-dist before_script: ./dev/travis/before_script.sh -script: ./dev/travis/script.sh +script: + # Set arguments for variants of phpunit based tests; '|| true' prevents failing script when leading test fails + - test $TEST_SUITE = "static" && TEST_FILTER='--filter "Magento\\Test\\Php\\LiveCodeTest"' || true + - test $TEST_SUITE = "functional" && TEST_FILTER='dev/tests/functional/testsuites/Magento/Mtf/TestSuite/InjectableTests.php' || true + + # The scripts for grunt/phpunit type tests + - if [ $TEST_SUITE != "js" ]; then phpunit -c dev/tests/$TEST_SUITE $TEST_FILTER; fi + - if [ $TEST_SUITE == "js" ]; then grunt $GRUNT_COMMAND; fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0634653275a9..2839ac5ee9d3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,3 +29,8 @@ If you are a new GitHub user, we recommend that you create your own [free github 3. Create and test your work. 4. Fork the Magento 2 repository according to [Fork a repository instructions](http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow [Create a pull request instructions](http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html#pull_request). 5. Once your contribution is received, Magento 2 development team will review the contribution and collaborate with you as needed to improve the quality of the contribution. + +## Code of Conduct + +Please note that this project is released with a Contributor Code of Conduct. We expect you to agree to its terms when participating in this project. +The full text is available in the repository [Wiki](https://github.com/magento/magento2/wiki/Magento-Code-of-Conduct). diff --git a/app/code/Magento/Backend/Ui/Component/Control/SaveSplitButton.php b/app/code/Magento/Backend/Ui/Component/Control/SaveSplitButton.php new file mode 100644 index 000000000000..9c6a1ad727c1 --- /dev/null +++ b/app/code/Magento/Backend/Ui/Component/Control/SaveSplitButton.php @@ -0,0 +1,112 @@ +saveTarget = $saveTarget; + } + + /** + * {@inheritdoc} + */ + public function getButtonData() + { + return [ + 'label' => __('Save & Continue'), + 'class' => 'save primary', + 'data_attribute' => [ + 'mage-init' => [ + 'buttonAdapter' => [ + 'actions' => [ + [ + 'targetName' => $this->saveTarget, + 'actionName' => 'save', + 'params' => [ + // first param is redirect flag + false, + ] + ] + ] + ] + ] + ], + 'class_name' => Container::SPLIT_BUTTON, + 'options' => $this->getOptions(), + 'sort_order' => 40, + ]; + } + + /** + * @return array + */ + private function getOptions() + { + $options = [ + [ + 'label' => __('Save & Close'), + 'data_attribute' => [ + 'mage-init' => [ + 'buttonAdapter' => [ + 'actions' => [ + [ + 'targetName' => $this->saveTarget, + 'actionName' => 'save', + 'params' => [ + // first param is redirect flag + true, + ], + ], + ], + ], + ], + ], + 'sort_order' => 10, + ], + [ + 'label' => __('Save & New'), + 'data_attribute' => [ + 'mage-init' => [ + 'buttonAdapter' => [ + 'actions' => [ + [ + 'targetName' => $this->saveTarget, + 'actionName' => 'save', + 'params' => [ + // first param is redirect flag, second is data that will be added to post + // request + true, + [ + 'redirect_to_new' => 1, + ], + ], + ], + ], + ], + ], + ], + 'sort_order' => 20, + ], + ]; + return $options; + } +} diff --git a/app/code/Magento/Backend/Ui/Component/Listing/Column/EditAction.php b/app/code/Magento/Backend/Ui/Component/Listing/Column/EditAction.php new file mode 100644 index 000000000000..ce51fa1bb35a --- /dev/null +++ b/app/code/Magento/Backend/Ui/Component/Listing/Column/EditAction.php @@ -0,0 +1,65 @@ +urlBuilder = $urlBuilder; + parent::__construct($context, $uiComponentFactory, $components, $data); + } + + /** + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + foreach ($dataSource['data']['items'] as &$item) { + if (isset($item[$item['id_field_name']])) { + $editUrlPath = $this->getData('config/editUrlPath') ?: '#'; + $item[$this->getData('name')] = [ + 'edit' => [ + 'href' => $this->urlBuilder->getUrl($editUrlPath, [ + $item['id_field_name'] => $item[$item['id_field_name']], + ]), + 'label' => __('Edit') + ] + ]; + unset($item); + } + } + } + return $dataSource; + } +} diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index d3c94c1e286e..bee4021963fa 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -18,6 +18,7 @@ "magento/module-translation": "100.2.*", "magento/module-require-js": "100.2.*", "magento/module-config": "100.2.*", + "magento/module-ui": "100.2.*", "magento/framework": "100.2.*" }, "suggest": { diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml index b951fd2c1949..c829a2f01fa9 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml @@ -43,7 +43,7 @@ data-validate="{required:true}" value="" placeholder="" - autocomplete="off" + autocomplete="new-password" /> diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml index 03d52ab9e570..2eebcdbe6574 100644 --- a/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml +++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/dialogs.phtml @@ -73,7 +73,7 @@
-
+
@@ -119,7 +119,7 @@
- +
diff --git a/app/code/Magento/Braintree/Block/Paypal/Button.php b/app/code/Magento/Braintree/Block/Paypal/Button.php index 8a90fc23ad12..efd9e473699c 100644 --- a/app/code/Magento/Braintree/Block/Paypal/Button.php +++ b/app/code/Magento/Braintree/Block/Paypal/Button.php @@ -5,13 +5,13 @@ */ namespace Magento\Braintree\Block\Paypal; -use Magento\Checkout\Model\Session; +use Magento\Braintree\Gateway\Config\PayPal\Config; +use Magento\Braintree\Model\Ui\ConfigProvider; use Magento\Catalog\Block\ShortcutInterface; -use Magento\Framework\View\Element\Template; +use Magento\Checkout\Model\Session; use Magento\Framework\Locale\ResolverInterface; -use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Framework\View\Element\Template; use Magento\Framework\View\Element\Template\Context; -use Magento\Braintree\Gateway\Config\PayPal\Config; use Magento\Payment\Model\MethodInterface; /** @@ -110,7 +110,7 @@ public function getContainerId() */ public function getLocale() { - return strtolower($this->localeResolver->getLocale()); + return $this->localeResolver->getLocale(); } /** diff --git a/app/code/Magento/Braintree/Model/LocaleResolver.php b/app/code/Magento/Braintree/Model/LocaleResolver.php new file mode 100644 index 000000000000..cebd90dffc70 --- /dev/null +++ b/app/code/Magento/Braintree/Model/LocaleResolver.php @@ -0,0 +1,93 @@ +resolver = $resolver; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function getDefaultLocalePath() + { + return $this->resolver->getDefaultLocalePath(); + } + + /** + * @inheritdoc + */ + public function setDefaultLocale($locale) + { + return $this->resolver->setDefaultLocale($locale); + } + + /** + * @inheritdoc + */ + public function getDefaultLocale() + { + return $this->resolver->getDefaultLocale(); + } + + /** + * @inheritdoc + */ + public function setLocale($locale = null) + { + return $this->resolver->setLocale($locale); + } + + /** + * Gets store's locale or the `en_US` locale if store's locale does not supported by PayPal. + * + * @return string + */ + public function getLocale() + { + $locale = $this->resolver->getLocale(); + $allowedLocales = $this->config->getValue('supported_locales'); + + return strpos($allowedLocales, $locale) !== false ? $locale : 'en_US'; + } + + /** + * @inheritdoc + */ + public function emulate($scopeId) + { + return $this->resolver->emulate($scopeId); + } + + /** + * @inheritdoc + */ + public function revert() + { + return $this->resolver->revert(); + } +} diff --git a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php index fc400cb375ea..e06b913db8ef 100644 --- a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php @@ -54,7 +54,7 @@ public function getConfig() 'title' => $this->config->getTitle(), 'isAllowShippingAddressOverride' => $this->config->isAllowToEditShippingAddress(), 'merchantName' => $this->config->getMerchantName(), - 'locale' => strtolower($this->resolver->getLocale()), + 'locale' => $this->resolver->getLocale(), 'paymentAcceptanceMarkSrc' => 'https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-medium.png', 'vaultCode' => self::PAYPAL_VAULT_CODE, diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php index 92d5f0a41716..ea46d8ee77a8 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php @@ -49,42 +49,35 @@ protected function setUp() /** * Run test getConfig method * - * @param array $config + * @param array $expected * @dataProvider getConfigDataProvider */ public function testGetConfig($expected) { - $this->config->expects(static::once()) - ->method('isActive') + $this->config->method('isActive') ->willReturn(true); - $this->config->expects(static::once()) - ->method('isAllowToEditShippingAddress') + $this->config->method('isAllowToEditShippingAddress') ->willReturn(true); - $this->config->expects(static::once()) - ->method('getMerchantName') + $this->config->method('getMerchantName') ->willReturn('Test'); - $this->config->expects(static::once()) - ->method('getTitle') + $this->config->method('getTitle') ->willReturn('Payment Title'); - $this->localeResolver->expects(static::once()) - ->method('getLocale') + $this->localeResolver->method('getLocale') ->willReturn('en_US'); - $this->config->expects(static::once()) - ->method('isSkipOrderReview') + $this->config->method('isSkipOrderReview') ->willReturn(false); - $this->config->expects(static::once()) - ->method('getPayPalIcon') + $this->config->method('getPayPalIcon') ->willReturn([ 'width' => 30, 'height' => 26, 'url' => 'https://icon.test.url' ]); - static::assertEquals($expected, $this->configProvider->getConfig()); + self::assertEquals($expected, $this->configProvider->getConfig()); } /** @@ -101,7 +94,7 @@ public function getConfigDataProvider() 'title' => 'Payment Title', 'isAllowShippingAddressOverride' => true, 'merchantName' => 'Test', - 'locale' => 'en_us', + 'locale' => 'en_US', 'paymentAcceptanceMarkSrc' => 'https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-medium.png', 'vaultCode' => ConfigProvider::PAYPAL_VAULT_CODE, diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index f1bbdf79b7d7..e91f59fb0740 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -34,7 +34,7 @@ processing sandbox 0 - + cvv,number @@ -66,6 +66,7 @@ 1 processorResponseCode,processorResponseText,paymentId processorResponseCode,processorResponseText,paymentId,payerEmail + en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL,no_NO,pl_PL,es_ES,sv_SE,tr_TR,pt_BR,ja_JP,id_ID,ko_KR,pt_PT,ru_RU,th_TH,zh_CN,zh_TW BraintreeCreditCardVaultFacade diff --git a/app/code/Magento/Braintree/etc/frontend/di.xml b/app/code/Magento/Braintree/etc/frontend/di.xml index 1983bb1f20e6..ea417c407dff 100644 --- a/app/code/Magento/Braintree/etc/frontend/di.xml +++ b/app/code/Magento/Braintree/etc/frontend/di.xml @@ -46,6 +46,7 @@ + Magento\Braintree\Model\LocaleResolver Magento_Braintree::paypal/button.phtml braintree.paypal.mini-cart @@ -54,4 +55,10 @@ BraintreePayPalFacade + + + + Magento\Braintree\Model\LocaleResolver + + diff --git a/app/code/Magento/Braintree/view/frontend/requirejs-config.js b/app/code/Magento/Braintree/view/frontend/requirejs-config.js index 8b347d799007..9fc38064677e 100644 --- a/app/code/Magento/Braintree/view/frontend/requirejs-config.js +++ b/app/code/Magento/Braintree/view/frontend/requirejs-config.js @@ -6,7 +6,7 @@ var config = { map: { '*': { - braintree: 'https://js.braintreegateway.com/js/braintree-2.25.0.min.js' + braintree: 'https://js.braintreegateway.com/js/braintree-2.32.0.min.js' } } }; diff --git a/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js index eaac1cd11608..3ac50fbcb47c 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button.js @@ -105,7 +105,11 @@ define( event.preventDefault(); registry.get(self.integrationName, function (integration) { - integration.paypal.initAuthFlow(); + try { + integration.paypal.initAuthFlow(); + } catch (e) { + $this.attr('disabled', 'disabled'); + } }); }); }.bind(this); 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 182ec5e7168e..9804ee848962 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 @@ -13,7 +13,8 @@ define([ 'Magento_Checkout/js/model/full-screen-loader', 'Magento_Checkout/js/model/payment/additional-validators', 'Magento_Vault/js/view/payment/vault-enabler', - 'Magento_Checkout/js/action/create-billing-address' + 'Magento_Checkout/js/action/create-billing-address', + 'mage/translate' ], function ( $, _, @@ -23,7 +24,8 @@ define([ fullScreenLoader, additionalValidators, VaultEnabler, - createBillingAddress + createBillingAddress, + $t ) { 'use strict'; @@ -403,7 +405,13 @@ define([ */ payWithPayPal: function () { if (additionalValidators.validate()) { - Braintree.checkout.paypal.initAuthFlow(); + 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/View/Options/Type/Date.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Date.php index 4ddbc5b42d4b..04434b12c9f6 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Date.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Date.php @@ -81,6 +81,9 @@ public function getCalendarDateHtml() $yearStart = $this->_catalogProductOptionTypeDate->getYearStart(); $yearEnd = $this->_catalogProductOptionTypeDate->getYearEnd(); + $dateFormat = $this->_localeDate->getDateFormat(\IntlDateFormatter::SHORT); + /** Escape RTL characters which are present in some locales and corrupt formatting */ + $escapedDateFormat = preg_replace('/[^MmDdYy\/\.\-]/', '', $dateFormat); $calendar = $this->getLayout()->createBlock( \Magento\Framework\View\Element\Html\Date::class )->setId( @@ -92,7 +95,7 @@ public function getCalendarDateHtml() )->setImage( $this->getViewFileUrl('Magento_Theme::calendar.png') )->setDateFormat( - $this->_localeDate->getDateFormat(\IntlDateFormatter::SHORT) + $escapedDateFormat )->setValue( $value )->setYearsRange( diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php index d307055f22bb..5a83fa3e34fa 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php @@ -144,6 +144,7 @@ public function execute() $this->messageManager->addSuccess(__('You duplicated the product.')); } } catch (\Magento\Framework\Exception\LocalizedException $e) { + $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); $this->messageManager->addError($e->getMessage()); $this->getDataPersistor()->set('catalog_product', $data); $redirectBack = $productId ? true : 'new'; diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 6df07e7b3a9f..bfbdd9e4688d 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -2509,13 +2509,15 @@ public function setTypeId($typeId) /** * {@inheritdoc} * - * @return \Magento\Catalog\Api\Data\ProductExtensionInterface|null + * @return \Magento\Catalog\Api\Data\ProductExtensionInterface */ public function getExtensionAttributes() { $extensionAttributes = $this->_getExtensionAttributes(); - if (!$extensionAttributes) { - return $this->extensionAttributesFactory->create(\Magento\Catalog\Api\Data\ProductInterface::class); + if (null === $extensionAttributes) { + /** @var \Magento\Catalog\Api\Data\ProductExtensionInterface $extensionAttributes */ + $extensionAttributes = $this->extensionAttributesFactory->create(ProductInterface::class); + $this->setExtensionAttributes($extensionAttributes); } return $extensionAttributes; } @@ -2639,4 +2641,68 @@ public function setAssociatedProductIds(array $productIds) $this->getExtensionAttributes()->setConfigurableProductLinks($productIds); return $this; } + + /** + * Get quantity and stock status data + * + * @return array|null + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function getQuantityAndStockStatus() + { + return $this->getData('quantity_and_stock_status'); + } + + /** + * Set quantity and stock status data + * + * @param array $quantityAndStockStatusData + * @return $this + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function setQuantityAndStockStatus($quantityAndStockStatusData) + { + $this->setData('quantity_and_stock_status', $quantityAndStockStatusData); + return $this; + } + + /** + * Get stock data + * + * @return array|null + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function getStockData() + { + return $this->getData('stock_data'); + } + + /** + * Set stock data + * + * @param array $stockData + * @return $this + * + * @deprecated as Product model shouldn't be responsible for stock status + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ + public function setStockData($stockData) + { + $this->setData('stock_data', $stockData); + return $this; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php index 4df469111720..eba000efa096 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php @@ -3,13 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Catalog\Model\Product\Attribute\Backend; use Magento\Catalog\Model\Product; /** * Quantity and Stock Status attribute processing + * + * @deprecated as this attribute should be removed + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process */ class Stock extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend { @@ -47,25 +51,6 @@ public function afterLoad($object) return parent::afterLoad($object); } - /** - * Prepare inventory data from custom attribute - * - * @param Product $object - * @return void - */ - public function beforeSave($object) - { - $stockData = $object->getData($this->getAttribute()->getAttributeCode()); - if (isset($stockData['qty']) && $stockData['qty'] === '') { - $stockData['qty'] = null; - } - if ($object->getStockData() !== null && $stockData !== null) { - $object->setStockData(array_replace((array)$object->getStockData(), (array)$stockData)); - } - $object->unsetData($this->getAttribute()->getAttributeCode()); - parent::beforeSave($object); - } - /** * Validate * diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php index 176a15c89526..efe20b4003d0 100644 --- a/app/code/Magento/Catalog/Model/Product/Copier.php +++ b/app/code/Magento/Catalog/Model/Product/Copier.php @@ -59,7 +59,9 @@ public function copy(\Magento\Catalog\Model\Product $product) /** @var \Magento\Catalog\Model\Product $duplicate */ $duplicate = $this->productFactory->create(); - $duplicate->setData($product->getData()); + $productData = $product->getData(); + $productData = $this->removeStockItem($productData); + $duplicate->setData($productData); $duplicate->setOptions([]); $duplicate->setIsDuplicate(true); $duplicate->setOriginalLinkId($product->getData($metadata->getLinkField())); @@ -116,4 +118,21 @@ private function getMetadataPool() } return $this->metadataPool; } + + /** + * Remove stock item + * + * @param array $productData + * @return array + */ + private function removeStockItem(array $productData) + { + if (isset($productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY])) { + $extensionAttributes = $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]; + if (null !== $extensionAttributes->getStockItem()) { + $extensionAttributes->setData('stock_item', null); + } + } + return $productData; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php index 52d97e86440e..cb6e76aebaad 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php @@ -155,7 +155,7 @@ public function prepareForCart() if ($this->_dateExists()) { if ($this->useCalendar()) { - $timestamp += (new \DateTime($value['date']))->getTimestamp(); + $timestamp += $this->_localeDate->date($value['date'], null, true, false)->getTimestamp(); } else { $timestamp += mktime(0, 0, 0, $value['month'], $value['day'], $value['year']); } diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index ceacfea5ec02..c91ae6254461 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -634,7 +634,7 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO } catch (LocalizedException $e) { throw $e; } catch (\Exception $e) { - throw new \Magento\Framework\Exception\CouldNotSaveException(__('Unable to save product')); + throw new \Magento\Framework\Exception\CouldNotSaveException(__('Unable to save product'), $e); } unset($this->instances[$product->getSku()]); unset($this->instancesById[$product->getId()]); diff --git a/app/code/Magento/Catalog/Setup/UpgradeData.php b/app/code/Magento/Catalog/Setup/UpgradeData.php index a17f80ae623d..67bf080a1228 100644 --- a/app/code/Magento/Catalog/Setup/UpgradeData.php +++ b/app/code/Magento/Catalog/Setup/UpgradeData.php @@ -5,10 +5,7 @@ */ namespace Magento\Catalog\Setup; -use Magento\Catalog\Api\Data\ProductAttributeInterface; -use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface; use Magento\Eav\Setup\EavSetup; -use Magento\Eav\Setup\EavSetupFactory; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\UpgradeDataInterface; @@ -30,7 +27,7 @@ class UpgradeData implements UpgradeDataInterface /** * EAV setup factory * - * @var EavSetupFactory + * @var \Magento\Eav\Setup\EavSetupFactory */ private $eavSetupFactory; @@ -43,12 +40,12 @@ class UpgradeData implements UpgradeDataInterface * Constructor * * @param CategorySetupFactory $categorySetupFactory - * @param EavSetupFactory $eavSetupFactory + * @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory * @param UpgradeWidgetData $upgradeWidgetData */ public function __construct( CategorySetupFactory $categorySetupFactory, - EavSetupFactory $eavSetupFactory, + \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory, UpgradeWidgetData $upgradeWidgetData ) { $this->categorySetupFactory = $categorySetupFactory; @@ -100,7 +97,7 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface if (version_compare($context->getVersion(), '2.0.2') < 0) { // set new resource model paths - /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + /** @var CategorySetup $categorySetup */ $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); $categorySetup->updateEntityType( \Magento\Catalog\Model\Category::ENTITY, @@ -141,69 +138,72 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface } if (version_compare($context->getVersion(), '2.0.3') < 0) { - /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + /** @var CategorySetup $categorySetup */ $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); $categorySetup->updateAttribute(3, 54, 'default_value', 1); } if (version_compare($context->getVersion(), '2.0.4') < 0) { - /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + $mediaBackendType = 'static'; + $mediaBackendModel = null; + /** @var CategorySetup $categorySetup */ $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); $categorySetup->updateAttribute( 'catalog_product', 'media_gallery', 'backend_type', - 'static' + $mediaBackendType ); $categorySetup->updateAttribute( 'catalog_product', 'media_gallery', - 'backend_model' + 'backend_model', + $mediaBackendModel ); } if (version_compare($context->getVersion(), '2.0.5', '<')) { - /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + /** @var CategorySetup $categorySetup */ $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); //Product Details tab $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'status', 'frontend_label', 'Enable Product', 5 ); $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'name', 'frontend_label', 'Product Name' ); - $attributeSetId = $categorySetup->getDefaultAttributeSetId(ProductAttributeInterface::ENTITY_TYPE_CODE); + $attributeSetId = $categorySetup->getDefaultAttributeSetId(\Magento\Catalog\Model\Product::ENTITY); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Product Details', 'visibility', 80 ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Product Details', 'news_from_date', 90 ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Product Details', 'news_to_date', 100 ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Product Details', 'country_of_manufacture', @@ -212,26 +212,26 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface //Content tab $categorySetup->addAttributeGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Content', 15 ); $categorySetup->updateAttributeGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Content', 'tab_group_code', 'basic' ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Content', 'description' ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Content', 'short_description', @@ -240,39 +240,39 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface //Images tab $groupId = (int)$categorySetup->getAttributeGroupByCode( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'image-management', 'attribute_group_id' ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, $groupId, 'image', 1 ); $categorySetup->updateAttributeGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, $groupId, 'attribute_group_name', 'Images' ); $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'image', 'frontend_label', 'Base' ); $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'small_image', 'frontend_label', 'Small' ); $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'image', 'frontend_input_renderer', null @@ -280,13 +280,13 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface //Design tab $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'page_layout', 'frontend_label', 'Layout' ); $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'custom_layout_update', 'frontend_label', 'Layout Update XML', @@ -295,47 +295,47 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface //Schedule Design Update tab $categorySetup->addAttributeGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Schedule Design Update', 55 ); $categorySetup->updateAttributeGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Schedule Design Update', 'tab_group_code', 'advanced' ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Schedule Design Update', 'custom_design_from', 20 ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Schedule Design Update', 'custom_design_to', 30 ); $categorySetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'custom_design', 'frontend_label', 'New Theme', 40 ); $categorySetup->addAttributeToGroup( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, $attributeSetId, 'Schedule Design Update', 'custom_design' ); $categorySetup->addAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'custom_layout', [ 'type' => 'varchar', @@ -344,7 +344,7 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface 'source' => \Magento\Catalog\Model\Product\Attribute\Source\Layout::class, 'required' => false, 'sort_order' => 50, - 'global' => ScopedAttributeInterface::SCOPE_STORE, + 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, 'group' => 'Schedule Design Update', 'is_used_in_grid' => true, 'is_visible_in_grid' => false, @@ -358,7 +358,7 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); $eavSetup->updateAttribute( - ProductAttributeInterface::ENTITY_TYPE_CODE, + \Magento\Catalog\Model\Product::ENTITY, 'meta_description', [ 'note' => 'Maximum 255 chars. Meta Description should optimally be between 150-160 characters' @@ -367,7 +367,7 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface } if (version_compare($context->getVersion(), '2.1.3') < 0) { - /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */ + /** @var CategorySetup $categorySetup */ $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]); $this->changePriceAttributeDefaultScope($categorySetup); } @@ -386,7 +386,7 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface * Set to 'No' 'Is Allowed Html on Store Front' option on product name attribute, because product name * is multi entity field (used in order, quote) and cannot be conditionally escaped in all places * - * @param ModuleDataSetupInterface $categorySetup + * @param ModuleDataSetupInterface $setup * @return void */ private function dissallowUsingHtmlForProductName(ModuleDataSetupInterface $setup) @@ -405,7 +405,7 @@ private function dissallowUsingHtmlForProductName(ModuleDataSetupInterface $setu } /** - * @param \Magento\Catalog\Setup\CategorySetup $categorySetup + * @param CategorySetup $categorySetup * @return void */ private function changePriceAttributeDefaultScope($categorySetup) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php index cc406d2def35..4f481caabf1d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php @@ -74,68 +74,4 @@ public function testAfterLoad() $this->assertEquals(1, $data[self::ATTRIBUTE_NAME]['is_in_stock']); $this->assertEquals(5, $data[self::ATTRIBUTE_NAME]['qty']); } - - public function testBeforeSave() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 5], - 'stock_data' => ['is_in_stock' => 2, 'qty' => 2], - ] - ); - $stockData = $object->getStockData(); - $this->assertEquals(2, $stockData['is_in_stock']); - $this->assertEquals(2, $stockData['qty']); - $this->assertNotEmpty($object->getData(self::ATTRIBUTE_NAME)); - - $this->model->beforeSave($object); - - $stockData = $object->getStockData(); - $this->assertEquals(1, $stockData['is_in_stock']); - $this->assertEquals(5, $stockData['qty']); - $this->assertNull($object->getData(self::ATTRIBUTE_NAME)); - } - - public function testBeforeSaveQtyIsEmpty() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => ''], - 'stock_data' => ['is_in_stock' => 2, 'qty' => ''], - ] - ); - - $this->model->beforeSave($object); - - $stockData = $object->getStockData(); - $this->assertNull($stockData['qty']); - } - - public function testBeforeSaveQtyIsZero() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0], - 'stock_data' => ['is_in_stock' => 2, 'qty' => 0], - ] - ); - - $this->model->beforeSave($object); - - $stockData = $object->getStockData(); - $this->assertEquals(0, $stockData['qty']); - } - - public function testBeforeSaveNoStockData() - { - $object = new \Magento\Framework\DataObject( - [ - self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0] - ] - ); - - $this->model->beforeSave($object); - $this->assertNull($object->getStockData()); - $this->assertNull($object->getData(self::ATTRIBUTE_NAME)); - } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php index 8978af9edc40..bdedb6c72aad 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php @@ -5,8 +5,12 @@ */ namespace Magento\Catalog\Test\Unit\Model\Product; +use Magento\Catalog\Api\Data\ProductInterface; use \Magento\Catalog\Model\Product\Copier; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class CopierTest extends \PHPUnit_Framework_TestCase { /** @@ -80,10 +84,28 @@ protected function setUp() public function testCopy() { + $stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class) + ->getMock(); + $extensionAttributes = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class) + ->setMethods(['getStockItem', 'setData']) + ->getMock(); + $extensionAttributes + ->expects($this->once()) + ->method('getStockItem') + ->willReturn($stockItem); + $extensionAttributes + ->expects($this->once()) + ->method('setData') + ->with('stock_item', null); + + $productData = [ + 'product data' => ['product data'], + ProductInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttributes, + ]; $this->productMock->expects($this->atLeastOnce())->method('getWebsiteIds'); $this->productMock->expects($this->atLeastOnce())->method('getCategoryIds'); $this->productMock->expects($this->any())->method('getData')->willReturnMap([ - ['', null, 'product data'], + ['', null, $productData], ['linkField', null, '1'], ]); @@ -135,7 +157,7 @@ public function testCopy() )->with( \Magento\Store\Model\Store::DEFAULT_STORE_ID ); - $duplicateMock->expects($this->once())->method('setData')->with('product data'); + $duplicateMock->expects($this->once())->method('setData')->with($productData); $this->copyConstructorMock->expects($this->once())->method('build')->with($this->productMock, $duplicateMock); $duplicateMock->expects($this->once())->method('getUrlKey')->willReturn('urk-key-1'); $duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2'); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index adb0d037067d..2377b2313b9a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -195,6 +195,11 @@ class ProductTest extends \PHPUnit_Framework_TestCase */ private $collectionFactoryMock; + /** + * @var ProductExtensionInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $extensionAttributes; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -392,8 +397,16 @@ protected function setUp() ->setMethods(['create']) ->getMock(); $this->mediaConfig = $this->getMock(\Magento\Catalog\Model\Product\Media\Config::class, [], [], '', false); - $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->extensionAttributes = $this->getMockBuilder(ProductExtensionInterface::class) + ->setMethods(['getStockItem']) + ->getMock(); + $this->extensionAttributesFactory + ->expects($this->any()) + ->method('create') + ->willReturn($this->extensionAttributes); + + $this->objectManagerHelper = new ObjectManagerHelper($this); $this->model = $this->objectManagerHelper->getObject( \Magento\Catalog\Model\Product::class, [ diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php index 93c3b5002462..d105822ecb99 100644 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/AbstractModifierTest.php @@ -56,6 +56,7 @@ protected function setUp() $this->productMock = $this->getMockBuilder(ProductInterface::class) ->setMethods([ 'getId', + 'getTypeId', 'getStoreId', 'getResource', 'getData', diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php index b823ddb68e91..8967147968f1 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Attributes/Listing.php @@ -41,7 +41,6 @@ public function __construct( parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data); $this->request = $request; $this->collection = $collectionFactory->create(); - $this->collection->setExcludeSetFilter((int)$this->request->getParam('template_id', 0)); } /** @@ -49,6 +48,9 @@ public function __construct( */ public function getData() { + $this->collection->setExcludeSetFilter((int)$this->request->getParam('template_id', 0)); + $this->collection->getSelect()->setPart('order', []); + $items = []; foreach ($this->getCollection()->getItems() as $attribute) { $items[] = $attribute->toArray(); diff --git a/app/code/Magento/Catalog/etc/mview.xml b/app/code/Magento/Catalog/etc/mview.xml index 4213faf9307b..7ae38a7f2d0e 100644 --- a/app/code/Magento/Catalog/etc/mview.xml +++ b/app/code/Magento/Catalog/etc/mview.xml @@ -36,7 +36,6 @@
-
diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml index 508ad26bf937..96055b73d363 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/design_config_form.xml @@ -27,7 +27,7 @@ - jpg jpeg gif png svg + jpg jpeg gif png 2097152 theme/design_config_fileUploader/save @@ -87,7 +87,7 @@ - jpg jpeg gif png svg + jpg jpeg gif png 2097152 theme/design_config_fileUploader/save @@ -147,7 +147,7 @@ - jpg jpeg gif png svg + jpg jpeg gif png 2097152 theme/design_config_fileUploader/save diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index b83b1b78e25b..dc49b86a6a69 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -1228,15 +1228,15 @@ protected function _saveLinks() 'linked_product_id' => $linkedId, 'link_type_id' => $linkId, ]; - if (!empty($linkPositions[$linkedKey])) { - $positionRows[] = [ - 'link_id' => $productLinkKeys[$linkKey], - 'product_link_attribute_id' => $positionAttrId[$linkId], - 'value' => $linkPositions[$linkedKey], - ]; - } - $nextLinkId++; } + if (!empty($linkPositions[$linkedKey])) { + $positionRows[] = [ + 'link_id' => $productLinkKeys[$linkKey], + 'product_link_attribute_id' => $positionAttrId[$linkId], + 'value' => $linkPositions[$linkedKey], + ]; + } + $nextLinkId++; } } } diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php b/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php index e22be4d004aa..64609da69d48 100644 --- a/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php +++ b/app/code/Magento/CatalogInventory/Model/Plugin/AfterProductLoad.php @@ -4,31 +4,28 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\CatalogInventory\Model\Plugin; +/** + * @deprecated Stock Item as a part of ExtensionAttributes is deprecated + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ class AfterProductLoad { /** * @var \Magento\CatalogInventory\Api\StockRegistryInterface */ - protected $stockRegistry; - - /** - * @var \Magento\Catalog\Api\Data\ProductExtensionFactory - */ - protected $productExtensionFactory; + private $stockRegistry; /** * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry - * @param \Magento\Catalog\Api\Data\ProductExtensionFactory $productExtensionFactory */ public function __construct( - \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, - \Magento\Catalog\Api\Data\ProductExtensionFactory $productExtensionFactory + \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry ) { $this->stockRegistry = $stockRegistry; - $this->productExtensionFactory = $productExtensionFactory; } /** @@ -40,10 +37,6 @@ public function __construct( public function afterLoad(\Magento\Catalog\Model\Product $product) { $productExtension = $product->getExtensionAttributes(); - if ($productExtension === null) { - $productExtension = $this->productExtensionFactory->create(); - } - // stockItem := \Magento\CatalogInventory\Api\Data\StockItemInterface $productExtension->setStockItem($this->stockRegistry->getStockItem($product->getId())); $product->setExtensionAttributes($productExtension); return $product; diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php b/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php index 15ef1d8a85eb..a48be126d8ee 100644 --- a/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php +++ b/app/code/Magento/CatalogInventory/Model/Plugin/AroundProductRepositorySave.php @@ -4,61 +4,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\CatalogInventory\Model\Plugin; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\CatalogInventory\Api\Data\StockItemInterface; -use Magento\CatalogInventory\Api\StockConfigurationInterface; -use Magento\CatalogInventory\Api\StockRegistryInterface; -use Magento\Framework\Exception\LocalizedException; -use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Exception\CouldNotSaveException; /** - * Plugin for Magento\Catalog\Api\ProductRepositoryInterface + * Plugin is needed for backward compatibility * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @deprecated Stock data should be processed using the module API + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process */ class AroundProductRepositorySave { /** - * @var StockRegistryInterface - */ - protected $stockRegistry; - - /** - * @var StoreManagerInterface - * @deprecated - */ - protected $storeManager; - - /** - * @var StockConfigurationInterface - */ - private $stockConfiguration; - - /** - * @param StockRegistryInterface $stockRegistry - * @param StoreManagerInterface $storeManager - * @param StockConfigurationInterface $stockConfiguration - */ - public function __construct( - StockRegistryInterface $stockRegistry, - StoreManagerInterface $storeManager, - StockConfigurationInterface $stockConfiguration - ) { - $this->stockRegistry = $stockRegistry; - $this->storeManager = $storeManager; - $this->stockConfiguration = $stockConfiguration; - } - - /** - * Save stock item information from received product - * - * Pay attention that in this code we mostly work with original product object to process stock item data, - * not with received result (saved product) because it is already contains new empty stock item object. + * Plugin is needed for backward compatibility * * @param ProductRepositoryInterface $subject * @param ProductInterface $result @@ -75,93 +38,7 @@ public function afterSave( ProductInterface $product, $saveOptions = false ) { - /* @var StockItemInterface $stockItem */ - $stockItem = $this->getStockItemToBeUpdated($product); - if (null === $stockItem) { - return $result; - } - - // set fields that the customer should not care about - $stockItem->setProductId($product->getId()); - $stockItem->setWebsiteId($this->stockConfiguration->getDefaultScopeId()); - - $this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem); - // since we just saved a portion of the product, force a reload of it before returning it - return $subject->get($product->getSku(), false, $product->getStoreId(), true); - } - - /** - * Return the stock item that needs to be updated. - * If the stock item does not need to be updated, return null. - * - * @param ProductInterface $product - * @return StockItemInterface|null - * @throws LocalizedException - */ - protected function getStockItemToBeUpdated($product) - { - // from the API, all the data we care about will exist as extension attributes of the original product - $stockItem = $this->getStockItemFromProduct($product); - - if ($stockItem === null) { - $stockItem = $this->stockRegistry->getStockItem($product->getId()); - if ($stockItem->getItemId() !== null) { - // we already have stock item info, so we return null since nothing more needs to be updated - $stockItem = null; - } - } - - return $stockItem; - } - - /** - * @param ProductInterface $product - * @return StockItemInterface - * @throws LocalizedException - */ - private function getStockItemFromProduct(ProductInterface $product) - { - $stockItem = null; - $extendedAttributes = $product->getExtensionAttributes(); - if ($extendedAttributes !== null) { - $stockItem = $extendedAttributes->getStockItem(); - if ($stockItem) { - $this->validateStockItem($product, $stockItem); - } - } - - return $stockItem; - } - - /** - * @param ProductInterface $product - * @param StockItemInterface $stockItem - * @throws LocalizedException - * @return void - */ - private function validateStockItem(ProductInterface $product, StockItemInterface $stockItem) - { - $defaultScopeId = $this->stockConfiguration->getDefaultScopeId(); - $defaultStockId = $this->stockRegistry->getStock($defaultScopeId)->getStockId(); - $stockId = $stockItem->getStockId(); - if ($stockId !== null && $stockId != $defaultStockId) { - throw new LocalizedException( - __('Invalid stock id: %1. Only default stock with id %2 allowed', $stockId, $defaultStockId) - ); - } - $stockItemId = $stockItem->getItemId(); - if ($stockItemId !== null && (!is_numeric($stockItemId) || $stockItemId <= 0)) { - throw new LocalizedException( - __('Invalid stock item id: %1. Should be null or numeric value greater than 0', $stockItemId) - ); - } - - $defaultStockItemId = $this->stockRegistry->getStockItem($product->getId())->getItemId(); - if ($defaultStockItemId && $stockItemId !== null && $defaultStockItemId != $stockItemId) { - throw new LocalizedException( - __('Invalid stock item id: %1. Assigned stock item id is %2', $stockItemId, $defaultStockItemId) - ); - } + return $subject->get($product->getSku(), false, $product->getStoreId()); } } diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php index 18c06b4b1ee1..2a8acc256783 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php @@ -22,6 +22,11 @@ class StockStatusBaseSelectProcessor implements BaseSelectProcessorInterface */ private $resource; + /** + * @var \Magento\CatalogInventory\Api\StockConfigurationInterface + */ + private $stockConfig; + /** * @var \Magento\Indexer\Model\ResourceModel\FrontendResource */ @@ -30,14 +35,18 @@ class StockStatusBaseSelectProcessor implements BaseSelectProcessorInterface /** * @param ResourceConnection $resource * @param null|\Magento\Indexer\Model\ResourceModel\FrontendResource $indexerStockFrontendResource + * @param \Magento\CatalogInventory\Api\StockConfigurationInterface|null $stockConfig */ public function __construct( ResourceConnection $resource, - \Magento\Indexer\Model\ResourceModel\FrontendResource $indexerStockFrontendResource = null + \Magento\Indexer\Model\ResourceModel\FrontendResource $indexerStockFrontendResource = null, + \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfig = null ) { $this->resource = $resource; $this->indexerStockFrontendResource = $indexerStockFrontendResource ?: ObjectManager::getInstance() ->get(\Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\FrontendResource::class); + $this->stockConfig = $stockConfig ?: ObjectManager::getInstance() + ->get(\Magento\CatalogInventory\Api\StockConfigurationInterface::class); } /** @@ -50,13 +59,15 @@ public function process(Select $select) { $stockStatusTable = $this->indexerStockFrontendResource->getMainTable(); - /** @var Select $select */ - $select->join( - ['stock' => $stockStatusTable], - sprintf('stock.product_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), - [] - ) - ->where('stock.stock_status = ?', Stock::STOCK_IN_STOCK); + if (!$this->stockConfig->isShowOutOfStock()) { + /** @var Select $select */ + $select->join( + ['stock' => $stockStatusTable], + sprintf('stock.product_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), + [] + )->where('stock.stock_status = ?', Stock::STOCK_IN_STOCK); + } + return $select; } } diff --git a/app/code/Magento/CatalogInventory/Model/Stock.php b/app/code/Magento/CatalogInventory/Model/Stock.php index 75c5d27b972d..d4568429e3a5 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock.php +++ b/app/code/Magento/CatalogInventory/Model/Stock.php @@ -32,7 +32,7 @@ class Stock extends AbstractExtensibleModel implements StockInterface * * @var string */ - protected $eventObject = 'stock'; + protected $_eventObject = 'stock'; const BACKORDERS_NO = 0; diff --git a/app/code/Magento/CatalogInventory/Model/Stock/Item.php b/app/code/Magento/CatalogInventory/Model/Stock/Item.php index e4cd70f71c64..b4b70041ce14 100644 --- a/app/code/Magento/CatalogInventory/Model/Stock/Item.php +++ b/app/code/Magento/CatalogInventory/Model/Stock/Item.php @@ -43,7 +43,7 @@ class Item extends AbstractExtensibleModel implements StockItemInterface * * @var string */ - protected $eventObject = 'item'; + protected $_eventObject = 'item'; /** * Store model manager diff --git a/app/code/Magento/CatalogInventory/Model/StockItemValidator.php b/app/code/Magento/CatalogInventory/Model/StockItemValidator.php new file mode 100644 index 000000000000..2cc4832159d5 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/StockItemValidator.php @@ -0,0 +1,74 @@ +stockConfiguration = $stockConfiguration; + $this->stockRegistry = $stockRegistry; + } + + /** + * Validate Stock item + * + * @param ProductInterface $product + * @param StockItemInterface $stockItem + * @throws LocalizedException + * @return void + */ + public function validate(ProductInterface $product, StockItemInterface $stockItem) + { + $defaultScopeId = $this->stockConfiguration->getDefaultScopeId(); + $defaultStockId = $this->stockRegistry->getStock($defaultScopeId)->getStockId(); + $stockId = $stockItem->getStockId(); + if ($stockId !== null && $stockId != $defaultStockId) { + throw new LocalizedException( + __('Invalid stock id: %1. Only default stock with id %2 allowed', $stockId, $defaultStockId) + ); + } + + $stockItemId = $stockItem->getItemId(); + if ($stockItemId !== null && (!is_numeric($stockItemId) || $stockItemId <= 0)) { + throw new LocalizedException( + __('Invalid stock item id: %1. Should be null or numeric value greater than 0', $stockItemId) + ); + } + + $defaultStockItemId = $this->stockRegistry->getStockItem($product->getId())->getItemId(); + if ($defaultStockItemId && $stockItemId !== null && $defaultStockItemId != $stockItemId) { + throw new LocalizedException( + __('Invalid stock item id: %1. Assigned stock item id is %2', $stockItemId, $defaultStockItemId) + ); + } + } +} diff --git a/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php new file mode 100644 index 000000000000..4d4628e25eff --- /dev/null +++ b/app/code/Magento/CatalogInventory/Observer/ProcessInventoryDataObserver.php @@ -0,0 +1,132 @@ +stockRegistry = $stockRegistry; + } + + /** + * Process stock item data + * + * @param EventObserver $observer + * @return void + */ + public function execute(EventObserver $observer) + { + $product = $observer->getEvent()->getProduct(); + $this->processStockData($product); + } + + /** + * Process stock item data + * + * Synchronize stock data from different sources (stock_data, quantity_and_stock_status, StockItem) and set it to + * stock_data key + * + * @param Product $product + * @return void + */ + private function processStockData(Product $product) + { + /** @var Item $stockItem */ + $stockItem = $this->stockRegistry->getStockItem($product->getId(), $product->getStore()->getWebsiteId()); + + $quantityAndStockStatus = $product->getData('quantity_and_stock_status'); + if (is_array($quantityAndStockStatus)) { + $quantityAndStockStatus = $this->prepareQuantityAndStockStatus($stockItem, $quantityAndStockStatus); + + if ($quantityAndStockStatus) { + $this->setStockDataToProduct($product, $stockItem, $quantityAndStockStatus); + } + } + $product->unsetData('quantity_and_stock_status'); + } + + /** + * Prepare quantity_and_stock_status data + * + * Remove not changed values from quantity_and_stock_status data + * Set null value for qty if passed empty + * + * @param StockItemInterface $stockItem + * @param array $quantityAndStockStatus + * @return array + */ + private function prepareQuantityAndStockStatus(StockItemInterface $stockItem, array $quantityAndStockStatus) + { + $stockItemId = $stockItem->getItemId(); + + if (null !== $stockItemId) { + if (isset($quantityAndStockStatus['is_in_stock']) + && $stockItem->getIsInStock() == $quantityAndStockStatus['is_in_stock'] + ) { + unset($quantityAndStockStatus['is_in_stock']); + } + if (isset($quantityAndStockStatus['qty']) + && $stockItem->getQty() == $quantityAndStockStatus['qty'] + ) { + unset($quantityAndStockStatus['qty']); + } + } + + if (array_key_exists('qty', $quantityAndStockStatus) && $quantityAndStockStatus['qty'] === '') { + $quantityAndStockStatus['qty'] = null; + } + return $quantityAndStockStatus; + } + + /** + * Set stock data to product + * + * First of all we take stock_data data, replace it from quantity_and_stock_status data (if was changed) and finally + * replace it with data from Stock Item object (only if Stock Item was changed) + * + * @param Product $product + * @param Item $stockItem + * @param array $quantityAndStockStatus + * @return void + */ + private function setStockDataToProduct(Product $product, Item $stockItem, array $quantityAndStockStatus) + { + $stockData = array_replace((array)$product->getData('stock_data'), $quantityAndStockStatus); + if ($stockItem->hasDataChanges()) { + $stockData = array_replace($stockData, $stockItem->getData()); + } + $product->setData('stock_data', $stockData); + } +} diff --git a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php index 57c3e7053cd1..5f1c4f4350b1 100644 --- a/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/SaveInventoryDataObserver.php @@ -3,43 +3,46 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\CatalogInventory\Observer; +use Magento\Catalog\Model\Product; +use Magento\CatalogInventory\Model\Stock\Item; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Event\ObserverInterface; use Magento\CatalogInventory\Api\StockConfigurationInterface; -use Magento\CatalogInventory\Api\StockIndexInterface; -use Magento\CatalogInventory\Api\StockItemRepositoryInterface; use Magento\CatalogInventory\Api\StockRegistryInterface; +use Magento\CatalogInventory\Model\StockItemValidator; use Magento\Framework\Event\Observer as EventObserver; +/** + * Saves stock data from a product to the Stock Item + * + * @deprecated Stock data should be processed using the module API + * @see StockItemInterface when you want to change the stock data + * @see StockStatusInterface when you want to read the stock data for representation layer (storefront) + * @see StockItemRepositoryInterface::save as extension point for customization of saving process + */ class SaveInventoryDataObserver implements ObserverInterface { - /** - * @var StockIndexInterface - * @deprecated - */ - protected $stockIndex; - /** * @var StockConfigurationInterface */ - protected $stockConfiguration; + private $stockConfiguration; /** * @var StockRegistryInterface */ - protected $stockRegistry; + private $stockRegistry; /** - * @var StockItemRepositoryInterface + * @var StockItemValidator */ - protected $stockItemRepository; + private $stockItemValidator; /** * @var array */ - protected $paramListToCheck = [ + private $paramListToCheck = [ 'use_config_min_qty' => [ 'item' => 'stock_data/min_qty', 'config' => 'stock_data/use_config_min_qty', @@ -71,73 +74,86 @@ class SaveInventoryDataObserver implements ObserverInterface ]; /** - * @param StockIndexInterface $stockIndex * @param StockConfigurationInterface $stockConfiguration * @param StockRegistryInterface $stockRegistry - * @param StockItemRepositoryInterface $stockItemRepository + * @param StockItemValidator $stockItemValidator */ public function __construct( - StockIndexInterface $stockIndex, StockConfigurationInterface $stockConfiguration, StockRegistryInterface $stockRegistry, - StockItemRepositoryInterface $stockItemRepository + StockItemValidator $stockItemValidator = null ) { - $this->stockIndex = $stockIndex; $this->stockConfiguration = $stockConfiguration; $this->stockRegistry = $stockRegistry; - $this->stockItemRepository = $stockItemRepository; + $this->stockItemValidator = $stockItemValidator ?: ObjectManager::getInstance()->get(StockItemValidator::class); } /** - * Saving product inventory data. Product qty calculated dynamically. + * Saving product inventory data + * + * Takes data from the stock_data property of a product and sets it to Stock Item. + * Validates and saves Stock Item object. * * @param EventObserver $observer - * @return $this + * @return void */ public function execute(EventObserver $observer) { $product = $observer->getEvent()->getProduct(); - if ($product->getStockData() === null) { - return $this; + $stockItem = $this->getStockItemToBeUpdated($product); + + if ($product->getStockData() !== null) { + $stockData = $this->getStockData($product); + $stockItem->addData($stockData); } + $this->stockItemValidator->validate($product, $stockItem); + $this->stockRegistry->updateStockItemBySku($product->getSku(), $stockItem); + } - $this->saveStockItemData($product); - return $this; + /** + * Return the stock item that needs to be updated + * + * @param Product $product + * @return Item + */ + private function getStockItemToBeUpdated(Product $product) + { + $extendedAttributes = $product->getExtensionAttributes(); + $stockItem = $extendedAttributes->getStockItem(); + + if ($stockItem === null) { + $stockItem = $this->stockRegistry->getStockItem($product->getId()); + } + return $stockItem; } /** - * Prepare stock item data for save + * Get stock data * - * @param \Magento\Catalog\Model\Product $product - * @return $this + * @param Product $product + * @return array */ - protected function saveStockItemData($product) + private function getStockData(Product $product) { - $stockItemData = $product->getStockData(); - $stockItemData['product_id'] = $product->getId(); + $stockData = $product->getStockData(); + $stockData['product_id'] = $product->getId(); - if (!isset($stockItemData['website_id'])) { - $stockItemData['website_id'] = $this->stockConfiguration->getDefaultScopeId(); + if (!isset($stockData['website_id'])) { + $stockData['website_id'] = $this->stockConfiguration->getDefaultScopeId(); } - $stockItemData['stock_id'] = $this->stockRegistry->getStock($stockItemData['website_id'])->getStockId(); + $stockData['stock_id'] = $this->stockRegistry->getStock($stockData['website_id'])->getStockId(); foreach ($this->paramListToCheck as $dataKey => $configPath) { if (null !== $product->getData($configPath['item']) && null === $product->getData($configPath['config'])) { - $stockItemData[$dataKey] = false; + $stockData[$dataKey] = false; } } $originalQty = $product->getData('stock_data/original_inventory_qty'); if (strlen($originalQty) > 0) { - $stockItemData['qty_correction'] = (isset($stockItemData['qty']) ? $stockItemData['qty'] : 0) + $stockData['qty_correction'] = (isset($stockData['qty']) ? $stockData['qty'] : 0) - $originalQty; } - - // todo resolve issue with builder and identity field name - $stockItem = $this->stockRegistry->getStockItem($stockItemData['product_id'], $stockItemData['website_id']); - - $stockItem->addData($stockItemData); - $this->stockItemRepository->save($stockItem); - return $this; + return $stockData; } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php index 375ed5937c27..408802d80bd5 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AfterProductLoadTest.php @@ -27,7 +27,7 @@ class AfterProductLoadTest extends \PHPUnit_Framework_TestCase protected $productExtensionFactoryMock; /** - * @var \Magento\Catalog\Api\Data\ProductExtension|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Catalog\Api\Data\ProductExtensionInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $productExtensionMock; @@ -53,7 +53,7 @@ protected function setUp() ->with($productId) ->willReturn($stockItemMock); - $this->productExtensionMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class) + $this->productExtensionMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtensionInterface::class) ->setMethods(['setStockItem']) ->getMock(); $this->productExtensionMock->expects($this->once()) @@ -75,23 +75,6 @@ protected function setUp() public function testAfterLoad() { - // test when extension attributes are not (yet) present in the product - $this->productMock->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn(null); - $this->productExtensionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->productExtensionMock); - - $this->assertEquals( - $this->productMock, - $this->plugin->afterLoad($this->productMock) - ); - } - - public function testAfterLoadWithExistingExtensionAttributes() - { - // test when extension attributes already exist $this->productMock->expects($this->once()) ->method('getExtensionAttributes') ->willReturn($this->productExtensionMock); diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php deleted file mode 100644 index 86a4ba3e25fe..000000000000 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/AroundProductRepositorySaveTest.php +++ /dev/null @@ -1,350 +0,0 @@ -stockRegistry = $this->getMockBuilder(StockRegistryInterface::class) - ->setMethods(['getStockItem', 'updateStockItemBySku']) - ->getMockForAbstractClass(); - $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) - ->getMockForAbstractClass(); - $this->stockConfiguration = $this->getMockBuilder(StockConfigurationInterface::class) - ->setMethods(['getDefaultScopeId']) - ->getMockForAbstractClass(); - - $this->plugin = new AroundProductRepositorySave( - $this->stockRegistry, - $this->storeManager, - $this->stockConfiguration - ); - - $this->savedProduct = $this->getMockBuilder(ProductInterface::class) - ->setMethods(['getExtensionAttributes', 'getStoreId']) - ->getMockForAbstractClass(); - - $this->productRepository = $this->getMockBuilder(ProductRepositoryInterface::class) - ->setMethods(['get']) - ->getMockForAbstractClass(); - $this->product = $this->getMockBuilder(ProductInterface::class) - ->setMethods(['getExtensionAttributes', 'getStoreId']) - ->getMockForAbstractClass(); - $this->productExtension = $this->getMockForAbstractClass( - ProductExtensionInterface::class, - [], - '', - false, - false, - true, - ['getStockItem'] - ); - $this->stockItem = $this->getMockBuilder(StockItemInterface::class) - ->setMethods(['setWebsiteId', 'getWebsiteId', 'getStockId']) - ->getMockForAbstractClass(); - $this->defaultStock = $this->getMockBuilder(StockInterface::class) - ->setMethods(['getStockId']) - ->getMockForAbstractClass(); - } - - public function testAfterSaveWhenProductHasNoStockItemNeedingToBeUpdated() - { - // pretend we have no extension attributes at all - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn(null); - $this->productExtension->expects($this->never())->method('getStockItem'); - - // pretend that the product already has existing stock item information - $this->stockRegistry->expects($this->once())->method('getStockItem')->willReturn($this->stockItem); - $this->stockItem->expects($this->once())->method('getItemId')->willReturn(1); - $this->stockItem->expects($this->never())->method('setProductId'); - $this->stockItem->expects($this->never())->method('setWebsiteId'); - - // expect that there are no changes to the existing stock item information - $result = $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - $this->assertEquals( - $this->savedProduct, - $result - ); - } - - public function testAfterSaveWhenProductHasNoPersistentStockItemInfo() - { - // pretend we do have extension attributes, but none for 'stock_item' - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn(null); - - $this->stockConfiguration->expects($this->once())->method('getDefaultScopeId')->willReturn(1); - $this->stockRegistry->expects($this->once())->method('getStockItem')->willReturn($this->stockItem); - $this->stockRegistry->expects($this->once())->method('updateStockItemBySku'); - - $this->stockItem->expects($this->once())->method('getItemId')->willReturn(null); - $this->stockItem->expects($this->once())->method('setProductId'); - $this->stockItem->expects($this->once())->method('setWebsiteId'); - $this->product->expects(($this->atLeastOnce()))->method('getStoreId')->willReturn(20); - - $newProductMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->disableOriginalConstructor()->getMock(); - $this->productRepository->expects($this->once())->method('get')->willReturn($newProductMock); - - $this->assertEquals( - $newProductMock, - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product) - ); - } - - public function testAfterSave() - { - $productId = 5494; - $storeId = 2; - $sku = 'my product that needs saving'; - $defaultScopeId = 100; - $this->stockConfiguration->expects($this->exactly(2)) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $storedStockItem = $this->getMockBuilder(StockItemInterface::class) - ->setMethods(['getItemId']) - ->getMockForAbstractClass(); - $storedStockItem->expects($this->once()) - ->method('getItemId') - ->willReturn(500); - $this->stockRegistry->expects($this->once()) - ->method('getStockItem') - ->willReturn($storedStockItem); - - $this->product->expects(($this->exactly(2)))->method('getId')->willReturn($productId); - $this->product->expects(($this->atLeastOnce()))->method('getStoreId')->willReturn($storeId); - $this->product->expects($this->atLeastOnce())->method('getSku')->willReturn($sku); - - $this->stockItem->expects($this->once())->method('setProductId')->with($productId); - $this->stockItem->expects($this->once())->method('setWebsiteId')->with($defaultScopeId); - - $this->stockRegistry->expects($this->once()) - ->method('updateStockItemBySku') - ->with($sku, $this->stockItem); - - $newProductMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->disableOriginalConstructor()->getMock(); - $this->productRepository->expects($this->once()) - ->method('get') - ->with($sku, false, $storeId, true) - ->willReturn($newProductMock); - - $this->assertEquals( - $newProductMock, - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product) - ); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid stock id: 100500. Only default stock with id 50 allowed - */ - public function testAfterSaveWithInvalidStockId() - { - $stockId = 100500; - $defaultScopeId = 100; - $defaultStockId = 50; - - $this->stockItem->expects($this->once()) - ->method('getStockId') - ->willReturn($stockId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - $this->stockConfiguration->expects($this->once()) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->defaultStock->expects($this->once()) - ->method('getStockId') - ->willReturn($defaultStockId); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid stock item id: 0. Should be null or numeric value greater than 0 - */ - public function testAfterSaveWithInvalidStockItemId() - { - $stockId = 80; - $stockItemId = 0; - $defaultScopeId = 100; - $defaultStockId = 80; - - $this->stockItem->expects($this->once()) - ->method('getStockId') - ->willReturn($stockId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - $this->stockConfiguration->expects($this->once()) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->defaultStock->expects($this->once()) - ->method('getStockId') - ->willReturn($defaultStockId); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $this->stockItem->expects($this->once()) - ->method('getItemId') - ->willReturn($stockItemId); - - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Invalid stock item id: 35. Assigned stock item id is 40 - */ - public function testAfterSaveWithNotAssignedStockItemId() - { - $stockId = 80; - $stockItemId = 35; - $defaultScopeId = 100; - $defaultStockId = 80; - $storedStockitemId = 40; - - $this->stockItem->expects($this->once()) - ->method('getStockId') - ->willReturn($stockId); - $this->stockRegistry->expects($this->once()) - ->method('getStock') - ->with($defaultScopeId) - ->willReturn($this->defaultStock); - $this->stockConfiguration->expects($this->once()) - ->method('getDefaultScopeId') - ->willReturn($defaultScopeId); - $this->defaultStock->expects($this->once()) - ->method('getStockId') - ->willReturn($defaultStockId); - - $this->product->expects($this->once()) - ->method('getExtensionAttributes') - ->willReturn($this->productExtension); - $this->productExtension->expects($this->once()) - ->method('getStockItem') - ->willReturn($this->stockItem); - - $this->stockItem->expects($this->once()) - ->method('getItemId') - ->willReturn($stockItemId); - - $storedStockItem = $this->getMockBuilder(StockItemInterface::class) - ->setMethods(['getItemId']) - ->getMockForAbstractClass(); - $storedStockItem->expects($this->once()) - ->method('getItemId') - ->willReturn($storedStockitemId); - $this->stockRegistry->expects($this->once()) - ->method('getStockItem') - ->willReturn($storedStockItem); - - $this->plugin->afterSave($this->productRepository, $this->savedProduct, $this->product); - } -} diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/ItemTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/ItemTest.php index 643e2f3b5328..65a7f5fc6c40 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/ItemTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Stock/ItemTest.php @@ -477,33 +477,37 @@ public function getQtyIncrementsDataProvider() * * @param $eventName * @param $methodName + * @param $objectName * * @dataProvider eventsDataProvider */ - public function testDispatchEvents($eventName, $methodName) + public function testDispatchEvents($eventName, $methodName, $objectName) { $isCalledWithRightPrefix = 0; + $isObjectNameRight = 0; $this->eventDispatcher->expects($this->any())->method('dispatch')->with( $this->callback(function ($arg) use (&$isCalledWithRightPrefix, $eventName) { $isCalledWithRightPrefix |= ($arg === $eventName); return true; }), - $this->anything() + $this->callback(function ($data) use (&$isObjectNameRight, $objectName) { + $isObjectNameRight |= isset($data[$objectName]); + return true; + }) ); $this->item->$methodName(); - $this->assertEquals( - 1, - (int) $isCalledWithRightPrefix, - sprintf("Event %s doesn't dispatched", $eventName) + $this->assertTrue( + ($isCalledWithRightPrefix && $isObjectNameRight), + sprintf('Event "%s" with object name "%s" doesn\'t dispatched properly', $eventName, $objectName) ); } public function eventsDataProvider() { return [ - ['cataloginventory_stock_item_save_before', 'beforeSave'], - ['cataloginventory_stock_item_save_after', 'afterSave'], + ['cataloginventory_stock_item_save_before', 'beforeSave', 'item'], + ['cataloginventory_stock_item_save_after', 'afterSave', 'item'], ]; } } diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/StockTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/StockTest.php index 1c3d9d05f5ae..b1eca1fdc754 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/StockTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/StockTest.php @@ -100,33 +100,37 @@ public function setUp() * * @param $eventName * @param $methodName + * @param $objectName * * @dataProvider eventsDataProvider */ - public function testDispatchEvents($eventName, $methodName) + public function testDispatchEvents($eventName, $methodName, $objectName) { $isCalledWithRightPrefix = 0; + $isObjectNameRight = 0; $this->eventDispatcher->expects($this->any())->method('dispatch')->with( $this->callback(function ($arg) use (&$isCalledWithRightPrefix, $eventName) { $isCalledWithRightPrefix |= ($arg === $eventName); return true; }), - $this->anything() + $this->callback(function ($data) use (&$isObjectNameRight, $objectName) { + $isObjectNameRight |= isset($data[$objectName]); + return true; + }) ); $this->stockModel->$methodName(); - $this->assertEquals( - 1, - (int) $isCalledWithRightPrefix, - sprintf("Event %s doesn't dispatched", $eventName) + $this->assertTrue( + ($isCalledWithRightPrefix && $isObjectNameRight), + sprintf('Event "%s" with object name "%s" doesn\'t dispatched properly', $eventName, $objectName) ); } public function eventsDataProvider() { return [ - ['cataloginventory_stock_save_before', 'beforeSave'], - ['cataloginventory_stock_save_after', 'afterSave'], + ['cataloginventory_stock_save_before', 'beforeSave', 'stock'], + ['cataloginventory_stock_save_after', 'afterSave', 'stock'], ]; } } diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 97af0f934ad9..3b5f2483ec57 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -33,6 +33,9 @@ + + + diff --git a/app/code/Magento/CatalogInventory/etc/extension_attributes.xml b/app/code/Magento/CatalogInventory/etc/extension_attributes.xml index 2e3078860490..11bebb96ce31 100644 --- a/app/code/Magento/CatalogInventory/etc/extension_attributes.xml +++ b/app/code/Magento/CatalogInventory/etc/extension_attributes.xml @@ -7,6 +7,12 @@ --> + diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/Plugin/GetInStockAttributeOptionsPlugin.php b/app/code/Magento/CatalogInventoryConfigurableProduct/Plugin/GetInStockAttributeOptionsPlugin.php deleted file mode 100644 index 50c63af7ecaf..000000000000 --- a/app/code/Magento/CatalogInventoryConfigurableProduct/Plugin/GetInStockAttributeOptionsPlugin.php +++ /dev/null @@ -1,74 +0,0 @@ -stockStatusCriteriaFactory = $stockStatusCriteriaFactory; - $this->stockStatusRepository = $stockStatusRepository; - } - - /** - * Retrieve in stock options for attribute - * - * @param AttributeOptionProviderInterface $subject - * @param array $options - * @return array - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterGetAttributeOptions(AttributeOptionProviderInterface $subject, array $options) - { - $sku = []; - foreach ($options as $option) { - $sku[] = $option['sku']; - } - $criteria = $this->stockStatusCriteriaFactory->create(); - $criteria->addFilter('stock_status', 'stock_status', '1'); - $criteria->addFilter('sku', 'sku', ['in' => $sku], 'public'); - $collection = $this->stockStatusRepository->getList($criteria); - - if (!$collection->getTotalCount()) { - return []; - } - $inStockSku = []; - foreach ($collection->getItems() as $inStockOption) { - $inStockSku[] = $inStockOption->getData('sku'); - } - foreach ($options as $key => $option) { - if (!in_array($options[$key]['sku'], $inStockSku)) { - unset($options[$key]); - } - } - $options = array_values($options); - - return $options; - } -} diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/README.md b/app/code/Magento/CatalogInventoryConfigurableProduct/README.md deleted file mode 100644 index 62d1f4e092f3..000000000000 --- a/app/code/Magento/CatalogInventoryConfigurableProduct/README.md +++ /dev/null @@ -1 +0,0 @@ -Magento_CatalogInventoryConfigurableProduct module allows to retrieve in stock options. diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/Test/Unit/Plugin/GetInStockAttributeOptionsPluginTest.php b/app/code/Magento/CatalogInventoryConfigurableProduct/Test/Unit/Plugin/GetInStockAttributeOptionsPluginTest.php deleted file mode 100644 index 637bebe61cf4..000000000000 --- a/app/code/Magento/CatalogInventoryConfigurableProduct/Test/Unit/Plugin/GetInStockAttributeOptionsPluginTest.php +++ /dev/null @@ -1,275 +0,0 @@ -stockStatusCriteriaFactory = $this->getMockBuilder(StockStatusCriteriaInterfaceFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMockForAbstractClass(); - $this->stockStatusRepository = $this->getMockBuilder(StockStatusRepositoryInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->attributeOptionProvider = $this->getMockBuilder(AttributeOptionProviderInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->stockStatusCriteria = $this->getMockBuilder(StockStatusCriteriaInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->stockStatusCollection = $this->getMockBuilder(StockStatusCollectionInterface::class) - ->disableOriginalConstructor() - ->setMethods(['getItems']) - ->getMockForAbstractClass(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->plugin = $this->objectManagerHelper->getObject( - GetInStockAttributeOptionsPlugin::class, - [ - 'stockStatusCriteriaFactory' => $this->stockStatusCriteriaFactory, - 'stockStatusRepository' => $this->stockStatusRepository, - ] - ); - } - - /** - * @param array $options - * @dataProvider testOptionsDataProvider - */ - public function testGetInStockAttributeOptions(array $options) - { - $expectedOptions = [ - [ - 'sku' => 'Configurable1-White', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '14', - 'option_title' => 'White' - ], - [ - 'sku' => 'Configurable1-Red', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '15', - 'option_title' => 'Red' - ] - ]; - $status1 = $this->getMockBuilder(StockStatusInterface::class) - ->setMethods(['getData']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $status2 = $this->getMockBuilder(StockStatusInterface::class) - ->setMethods(['getData']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $statuses = [$status1, $status2]; - $this->stockStatusCriteriaFactory->expects($this->once()) - ->method('create') - ->willReturn($this->stockStatusCriteria); - $this->stockStatusCriteria->expects($this->atLeastOnce()) - ->method('addFilter') - ->willReturnSelf(); - $this->stockStatusRepository->expects($this->once()) - ->method('getList') - ->willReturn($this->stockStatusCollection); - $this->stockStatusCollection->expects($this->once()) - ->method('getTotalCount') - ->willReturn(2); - $this->stockStatusCollection->expects($this->any()) - ->method('getItems') - ->willReturn($statuses); - $status1->expects($this->atLeastOnce()) - ->method('getData') - ->willReturn('Configurable1-White'); - $status2->expects($this->atLeastOnce()) - ->method('getData') - ->willReturn('Configurable1-Red'); - - $this->assertEquals( - $expectedOptions, - $this->plugin->afterGetAttributeOptions($this->attributeOptionProvider, $options) - ); - } - - /** - * @param array $options - * @dataProvider testOptionsDataProvider - */ - public function testGetInStockAttributeOptionsWithAllOutOfStock(array $options) - { - $expectedOptions = []; - $this->stockStatusCriteriaFactory->expects($this->once()) - ->method('create') - ->willReturn($this->stockStatusCriteria); - $this->stockStatusCriteria->expects($this->atLeastOnce()) - ->method('addFilter') - ->willReturnSelf(); - $this->stockStatusRepository->expects($this->once()) - ->method('getList') - ->willReturn($this->stockStatusCollection); - $this->stockStatusCollection->expects($this->once()) - ->method('getTotalCount') - ->willReturn(0); - - $this->assertEquals( - $expectedOptions, - $this->plugin->afterGetAttributeOptions($this->attributeOptionProvider, $options) - ); - } - - /** - * @param array $options - * @dataProvider testOptionsDataProvider - */ - public function testGetInStockAttributeOptionsWithAllInStock(array $options) - { - $expectedOptions = [ - [ - 'sku' => 'Configurable1-Black', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '13', - 'option_title' => 'Black' - ], - [ - 'sku' => 'Configurable1-White', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '14', - 'option_title' => 'White' - ], - [ - 'sku' => 'Configurable1-Red', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '15', - 'option_title' => 'Red' - ] - ]; - $status1 = $this->getMockBuilder(StockStatusInterface::class) - ->setMethods(['getData']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $status2 = $this->getMockBuilder(StockStatusInterface::class) - ->setMethods(['getData']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $status3 = $this->getMockBuilder(StockStatusInterface::class) - ->setMethods(['getData']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $statuses = [$status1, $status2, $status3]; - $this->stockStatusCriteriaFactory->expects($this->once()) - ->method('create') - ->willReturn($this->stockStatusCriteria); - $this->stockStatusCriteria->expects($this->atLeastOnce()) - ->method('addFilter') - ->willReturnSelf(); - $this->stockStatusRepository->expects($this->once()) - ->method('getList') - ->willReturn($this->stockStatusCollection); - $this->stockStatusCollection->expects($this->once()) - ->method('getTotalCount') - ->willReturn(3); - $this->stockStatusCollection->expects($this->any()) - ->method('getItems') - ->willReturn($statuses); - $status1->expects($this->atLeastOnce()) - ->method('getData') - ->willReturn('Configurable1-Black'); - $status2->expects($this->atLeastOnce()) - ->method('getData') - ->willReturn('Configurable1-White'); - $status3->expects($this->atLeastOnce()) - ->method('getData') - ->willReturn('Configurable1-Red'); - - $this->assertEquals( - $expectedOptions, - $this->plugin->afterGetAttributeOptions($this->attributeOptionProvider, $options) - ); - } - - /** - * @return array - */ - public function testOptionsDataProvider() - { - return [ - [ - [ - [ - 'sku' => 'Configurable1-Black', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '13', - 'option_title' => 'Black' - ], - [ - 'sku' => 'Configurable1-White', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '14', - 'option_title' => 'White' - ], - [ - 'sku' => 'Configurable1-Red', - 'product_id' => 4, - 'attribute_code' => 'color', - 'value_index' => '15', - 'option_title' => 'Red' - ] - ] - ] - ]; - } -} diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/CatalogInventoryConfigurableProduct/etc/frontend/di.xml deleted file mode 100644 index ad77ef258aa4..000000000000 --- a/app/code/Magento/CatalogInventoryConfigurableProduct/etc/frontend/di.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - diff --git a/app/code/Magento/CatalogRule/etc/mview.xml b/app/code/Magento/CatalogRule/etc/mview.xml index 0fc3be87057b..2990c03ae015 100644 --- a/app/code/Magento/CatalogRule/etc/mview.xml +++ b/app/code/Magento/CatalogRule/etc/mview.xml @@ -18,7 +18,6 @@
-
diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php index 9fff759e22d5..972d3a2bfd5b 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php @@ -101,7 +101,7 @@ public function apply( 'search_index.entity_id = %1$s.entity_id AND %1$s.attribute_id = %2$d AND %1$s.store_id = %3$d', $alias, $attribute->getId(), - $this->storeManager->getWebsite()->getId() + $this->storeManager->getStore()->getId() ); $select->joinLeft( [$alias => $this->frontendResource->getMainTable()], diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/TermDropdownStrategyTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/TermDropdownStrategyTest.php index 4228d520008a..51ac30083c32 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/TermDropdownStrategyTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/TermDropdownStrategyTest.php @@ -11,7 +11,7 @@ use Magento\Framework\Search\Request\FilterInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Api\Data\StoreInterface; use Magento\Framework\DB\Select; use Magento\Eav\Model\Config as EavConfig; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; @@ -85,7 +85,7 @@ public function testApply() $attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->getMock(); - $website = $this->getMockBuilder(WebsiteInterface::class) + $store = $this->getMockBuilder(StoreInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -99,9 +99,9 @@ public function testApply() ->method('getAttribute') ->willReturn($attribute); $this->storeManager->expects($this->once()) - ->method('getWebsite') - ->willReturn($website); - $website->expects($this->once()) + ->method('getStore') + ->willReturn($store); + $store->expects($this->once()) ->method('getId') ->willReturn(1); $attribute->expects($this->once()) diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php index d600cc892be7..77ea3e1c312e 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/AnchorUrlRewriteGenerator.php @@ -41,7 +41,7 @@ public function __construct( } /** - * Generate list based on categories + * Generate product rewrites for anchor categories * * @param int $storeId * @param Product $product diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/CanonicalUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/CanonicalUrlRewriteGenerator.php index 69705c072e49..41e1a880d7ad 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/CanonicalUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/CanonicalUrlRewriteGenerator.php @@ -30,7 +30,7 @@ public function __construct(ProductUrlPathGenerator $productUrlPathGenerator, Ur } /** - * Generate list based on store view + * Generate product rewrites without categories * * @param int $storeId * @param Product $product diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php index 1d60565f28b9..ddad00b42a1d 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/CategoriesUrlRewriteGenerator.php @@ -31,7 +31,7 @@ public function __construct(ProductUrlPathGenerator $productUrlPathGenerator, Ur } /** - * Generate list based on categories + * Generate product rewrites with categories * * @param int $storeId * @param Product $product diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php index 70e4ec3d9287..d163ef8f067f 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/Product/CurrentUrlRewritesRegenerator.php @@ -6,6 +6,7 @@ namespace Magento\CatalogUrlRewrite\Model\Product; use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\CategoryRepository; use Magento\Catalog\Model\Product; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\UrlRewrite\Model\OptionProvider; @@ -56,19 +57,24 @@ class CurrentUrlRewritesRegenerator /** @var \Magento\UrlRewrite\Model\MergeDataProvider */ private $mergeDataProviderPrototype; + /** @var CategoryRepository */ + private $categoryRepository; + /** * @param UrlFinderInterface $urlFinder * @param ProductUrlPathGenerator $productUrlPathGenerator * @param UrlRewriteFactory $urlRewriteFactory * @param UrlRewriteFinder|null $urlRewriteFinder * @param \Magento\UrlRewrite\Model\MergeDataProviderFactory|null $mergeDataProviderFactory + * @param CategoryRepository|null $categoryRepository */ public function __construct( UrlFinderInterface $urlFinder, ProductUrlPathGenerator $productUrlPathGenerator, UrlRewriteFactory $urlRewriteFactory, UrlRewriteFinder $urlRewriteFinder = null, - MergeDataProviderFactory $mergeDataProviderFactory = null + MergeDataProviderFactory $mergeDataProviderFactory = null, + CategoryRepository $categoryRepository = null ) { $this->urlFinder = $urlFinder; $this->productUrlPathGenerator = $productUrlPathGenerator; @@ -78,11 +84,12 @@ public function __construct( if (!isset($mergeDataProviderFactory)) { $mergeDataProviderFactory = ObjectManager::getInstance()->get(MergeDataProviderFactory::class); } + $this->categoryRepository = $categoryRepository ?: ObjectManager::getInstance()->get(CategoryRepository::class); $this->mergeDataProviderPrototype = $mergeDataProviderFactory->create(); } /** - * Generate list based on current rewrites + * Generate product rewrites based on current rewrites without anchor categories * * @param int $storeId * @param Product $product @@ -115,6 +122,52 @@ public function generate($storeId, Product $product, ObjectRegistry $productCate return $mergeDataProvider->getData(); } + /** + * Generate product rewrites for anchor categories based on current rewrites + * + * @param int $storeId + * @param Product $product + * @param ObjectRegistry $productCategories + * @param int|null $rootCategoryId + * @return UrlRewrite[] + */ + public function generateAnchor( + $storeId, + Product $product, + ObjectRegistry $productCategories, + $rootCategoryId = null + ) { + $anchorCategoryIds = []; + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + + $currentUrlRewrites = $this->urlRewriteFinder->findAllByData( + $product->getEntityId(), + $storeId, + ProductUrlRewriteGenerator::ENTITY_TYPE, + $rootCategoryId + ); + + foreach ($productCategories->getList() as $productCategory) { + $anchorCategoryIds = array_merge($productCategory->getAnchorsAbove(), $anchorCategoryIds); + } + + foreach ($currentUrlRewrites as $currentUrlRewrite) { + $metadata = $currentUrlRewrite->getMetadata(); + if (isset($metadata['category_id']) && $metadata['category_id'] > 0) { + $category = $this->categoryRepository->get($metadata['category_id'], $storeId); + if (in_array($category->getId(), $anchorCategoryIds)) { + $mergeDataProvider->merge( + $currentUrlRewrite->getIsAutogenerated() + ? $this->generateForAutogenerated($currentUrlRewrite, $storeId, $category, $product) + : $this->generateForCustom($currentUrlRewrite, $storeId, $category, $product) + ); + } + } + } + + return $mergeDataProvider->getData(); + } + /** * @param UrlRewrite $url * @param int $storeId diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php index eb9a1881f5ba..2795bb02e961 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ProductScopeRewriteGenerator.php @@ -171,7 +171,14 @@ public function generateForSpecificStoreView($storeId, $productCategories, Produ $mergeDataProvider->merge( $this->anchorUrlRewriteGenerator->generate($storeId, $product, $productCategories) ); - + $mergeDataProvider->merge( + $this->currentUrlRewritesRegenerator->generateAnchor( + $storeId, + $product, + $productCategories, + $rootCategoryId + ) + ); return $mergeDataProvider->getData(); } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php index e1a670e9d359..c8816b8e4daf 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php @@ -80,11 +80,11 @@ public function __construct( public function generateProductUrlRewrites(\Magento\Catalog\Model\Category $category) { $mergeDataProvider = clone $this->mergeDataProviderPrototype; - $this->isSkippedProduct = []; + $this->isSkippedProduct[$category->getEntityId()] = []; $saveRewriteHistory = $category->getData('save_rewrites_history'); $storeId = $category->getStoreId(); if ($category->getAffectedProductIds()) { - $this->isSkippedProduct = $category->getAffectedProductIds(); + $this->isSkippedProduct[$category->getEntityId()] = $category->getAffectedProductIds(); $collection = $this->productCollectionFactory->create() ->setStoreId($storeId) ->addIdFilter($category->getAffectedProductIds()) @@ -137,17 +137,25 @@ public function getCategoryProductsUrlRewrites( $rootCategoryId = null ) { $mergeDataProvider = clone $this->mergeDataProviderPrototype; + /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $productCollection */ - $productCollection = $category->getProductCollection() + $productCollection = $this->productCollectionFactory->create(); + + $productCollection->addCategoriesFilter(['eq' => [$category->getEntityId()]]) + ->setStoreId($storeId) ->addAttributeToSelect('name') ->addAttributeToSelect('visibility') ->addAttributeToSelect('url_key') ->addAttributeToSelect('url_path'); + foreach ($productCollection as $product) { - if (in_array($product->getId(), $this->isSkippedProduct)) { + if ( + isset($this->isSkippedProduct[$category->getEntityId()]) && + in_array($product->getId(), $this->isSkippedProduct[$category->getEntityId()]) + ) { continue; } - $this->isSkippedProduct[] = $product->getId(); + $this->isSkippedProduct[$category->getEntityId()][] = $product->getId(); $product->setStoreId($storeId); $product->setData('save_rewrites_history', $saveRewriteHistory); $mergeDataProvider->merge( diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php index 16f6c5a1d9d0..d2334c862b17 100644 --- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/ProductScopeRewriteGeneratorTest.php @@ -137,6 +137,8 @@ public function testGenerationForGlobalScope() ->setStoreId(3); $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate') ->will($this->returnValue([$current])); + $this->currentUrlRewritesRegenerator->expects($this->any())->method('generateAnchor') + ->will($this->returnValue([$current])); $anchorCategories = new \Magento\UrlRewrite\Service\V1\Data\UrlRewrite([], $this->serializer); $anchorCategories->setRequestPath('category-4') ->setStoreId(4); @@ -178,6 +180,8 @@ public function testGenerationForSpecificStore() ->will($this->returnValue([])); $this->currentUrlRewritesRegenerator->expects($this->any())->method('generate') ->will($this->returnValue([])); + $this->currentUrlRewritesRegenerator->expects($this->any())->method('generateAnchor') + ->will($this->returnValue([])); $this->anchorUrlRewriteGenerator->expects($this->any())->method('generate') ->will($this->returnValue([])); diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index 2bf4311d35d7..5809ecbe9fef 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -237,6 +237,12 @@ public function createCollection() $conditions->collectValidatedAttributes($collection); $this->sqlBuilder->attachConditionToCollection($collection, $conditions); + /** + * Prevent retrieval of duplicate records. This may occur when multiselect product attribute matches + * several allowed values from condition simultaneously + */ + $collection->distinct(true); + return $collection; } diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php index d6177d5c3e0e..ee87842c9bf2 100644 --- a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php +++ b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php @@ -269,6 +269,7 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP 'addStoreFilter', 'setPageSize', 'setCurPage', + 'distinct' ])->disableOriginalConstructor() ->getMock(); $collection->expects($this->once())->method('setVisibility') @@ -282,6 +283,7 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP $collection->expects($this->once())->method('addStoreFilter')->willReturnSelf(); $collection->expects($this->once())->method('setPageSize')->with($expectedPageSize)->willReturnSelf(); $collection->expects($this->once())->method('setCurPage')->willReturnSelf(); + $collection->expects($this->once())->method('distinct')->willReturnSelf(); $this->collectionFactory->expects($this->once())->method('create')->willReturn($collection); $this->productsList->setData('conditions_encoded', 'some_serialized_conditions'); diff --git a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php index a2880c28844a..888b7fdbd094 100644 --- a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php +++ b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php @@ -315,21 +315,35 @@ protected function getMultilineFieldConfig($attributeCode, array $attributeConfi */ protected function getDefaultValue($attributeCode) { + if ($attributeCode === 'country_id') { + return $this->directoryHelper->getDefaultCountry(); + } + + $customer = $this->getCustomer(); + if ($customer === null) { + return null; + } + + $attributeValue = null; switch ($attributeCode) { + case 'prefix': + $attributeValue = $customer->getPrefix(); + break; case 'firstname': - if ($this->getCustomer()) { - return $this->getCustomer()->getFirstname(); - } + $attributeValue = $customer->getFirstname(); + break; + case 'middlename': + $attributeValue = $customer->getMiddlename(); break; case 'lastname': - if ($this->getCustomer()) { - return $this->getCustomer()->getLastname(); - } + $attributeValue = $customer->getLastname(); + break; + case 'suffix': + $attributeValue = $customer->getSuffix(); break; - case 'country_id': - return $this->directoryHelper->getDefaultCountry(); } - return null; + + return $attributeValue; } /** diff --git a/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js b/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js index 6784ca111674..9c0ef8bb58c0 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js @@ -31,14 +31,14 @@ define([ if ($.isEmptyObject(data)) { data = { - 'selectedShippingAddress': null, - 'shippingAddressFromData': null, - 'newCustomerShippingAddress': null, - 'selectedShippingRate': null, - 'selectedPaymentMethod': null, - 'selectedBillingAddress': null, - 'billingAddressFormData': null, - 'newCustomerBillingAddress': null + 'selectedShippingAddress': null, //Selected shipping address pulled from persistence storage + 'shippingAddressFromData': null, //Shipping address pulled from persistence storage + 'newCustomerShippingAddress': null, //Shipping address pulled from persistence storage for customer + 'selectedShippingRate': null, //Shipping rate pulled from persistence storage + 'selectedPaymentMethod': null, //Payment method pulled from persistence storage + 'selectedBillingAddress': null, //Selected billing address pulled from persistence storage + 'billingAddressFromData': null, //Billing address pulled from persistence storage + 'newCustomerBillingAddress': null //Billing address pulled from persistence storage for new customer }; saveData(data); } @@ -48,6 +48,8 @@ define([ return { /** + * Setting the selected shipping address pulled from persistence storage + * * @param {Object} data */ setSelectedShippingAddress: function (data) { @@ -58,6 +60,8 @@ define([ }, /** + * Pulling the selected shipping address from persistence storage + * * @return {*} */ getSelectedShippingAddress: function () { @@ -65,6 +69,8 @@ define([ }, /** + * Setting the shipping address pulled from persistence storage + * * @param {Object} data */ setShippingAddressFromData: function (data) { @@ -75,6 +81,8 @@ define([ }, /** + * Pulling the shipping address from persistence storage + * * @return {*} */ getShippingAddressFromData: function () { @@ -82,6 +90,8 @@ define([ }, /** + * Setting the shipping address pulled from persistence storage for new customer + * * @param {Object} data */ setNewCustomerShippingAddress: function (data) { @@ -92,6 +102,8 @@ define([ }, /** + * Pulling the shipping address from persistence storage for new customer + * * @return {*} */ getNewCustomerShippingAddress: function () { @@ -99,6 +111,8 @@ define([ }, /** + * Setting the selected shipping rate pulled from persistence storage + * * @param {Object} data */ setSelectedShippingRate: function (data) { @@ -109,6 +123,8 @@ define([ }, /** + * Pulling the selected shipping rate from local storage + * * @return {*} */ getSelectedShippingRate: function () { @@ -116,6 +132,8 @@ define([ }, /** + * Setting the selected payment method pulled from persistence storage + * * @param {Object} data */ setSelectedPaymentMethod: function (data) { @@ -126,6 +144,8 @@ define([ }, /** + * Pulling the payment method from persistence storage + * * @return {*} */ getSelectedPaymentMethod: function () { @@ -133,6 +153,8 @@ define([ }, /** + * Setting the selected billing address pulled from persistence storage + * * @param {Object} data */ setSelectedBillingAddress: function (data) { @@ -143,6 +165,8 @@ define([ }, /** + * Pulling the selected billing address from persistence storage + * * @return {*} */ getSelectedBillingAddress: function () { @@ -150,6 +174,8 @@ define([ }, /** + * Setting the billing address pulled from persistence storage + * * @param {Object} data */ setBillingAddressFromData: function (data) { @@ -160,6 +186,8 @@ define([ }, /** + * Pulling the billing address from persistence storage + * * @return {*} */ getBillingAddressFromData: function () { @@ -167,6 +195,8 @@ define([ }, /** + * Setting the billing address pulled from persistence storage for new customer + * * @param {Object} data */ setNewCustomerBillingAddress: function (data) { @@ -177,6 +207,8 @@ define([ }, /** + * Pulling the billing address from persistence storage for new customer + * * @return {*} */ getNewCustomerBillingAddress: function () { @@ -184,6 +216,8 @@ define([ }, /** + * Pulling the email address from persistence storage + * * @return {*} */ getValidatedEmailValue: function () { @@ -193,6 +227,8 @@ define([ }, /** + * Setting the email address pulled from persistence storage + * * @param {String} email */ setValidatedEmailValue: function (email) { @@ -203,6 +239,8 @@ define([ }, /** + * Pulling the email input field value from persistence storage + * * @return {*} */ getInputFieldEmailValue: function () { @@ -212,6 +250,8 @@ define([ }, /** + * Setting the email input field value pulled from persistence storage + * * @param {String} email */ setInputFieldEmailValue: function (email) { diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html index 17754b81849f..f2baf5d50030 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html @@ -5,7 +5,7 @@ */ -->
- +

,
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html index 4c3ce84baac4..f9400227fae0 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html @@ -5,7 +5,7 @@ */ -->
- +

,
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html index c3afce6c4537..ec41cae0bdc5 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html @@ -5,7 +5,7 @@ */ --> - +

,
diff --git a/app/code/Magento/Cms/Model/BlockRepository.php b/app/code/Magento/Cms/Model/BlockRepository.php index a06c5fac1bc4..5f8ec399e9bd 100644 --- a/app/code/Magento/Cms/Model/BlockRepository.php +++ b/app/code/Magento/Cms/Model/BlockRepository.php @@ -154,25 +154,10 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $criteria $this->collectionProcessor->process($criteria, $collection); - $blocks = []; - /** @var Block $blockModel */ - foreach ($collection as $blockModel) { - $blockData = $this->dataBlockFactory->create(); - $this->dataObjectHelper->populateWithArray( - $blockData, - $blockModel->getData(), - \Magento\Cms\Api\Data\BlockInterface::class - ); - $blocks[] = $this->dataObjectProcessor->buildOutputDataArray( - $blockData, - \Magento\Cms\Api\Data\BlockInterface::class - ); - } - /** @var Data\BlockSearchResultsInterface $searchResults */ $searchResults = $this->searchResultsFactory->create(); $searchResults->setSearchCriteria($criteria); - $searchResults->setItems($blocks); + $searchResults->setItems($collection->getItems()); $searchResults->setTotalCount($collection->getSize()); return $searchResults; } diff --git a/app/code/Magento/Cms/Model/PageRepository.php b/app/code/Magento/Cms/Model/PageRepository.php index 033289bf8fb8..521a975c885d 100644 --- a/app/code/Magento/Cms/Model/PageRepository.php +++ b/app/code/Magento/Cms/Model/PageRepository.php @@ -157,25 +157,10 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $criteria $this->collectionProcessor->process($criteria, $collection); - $pages = []; - /** @var Page $pageModel */ - foreach ($collection as $pageModel) { - $pageData = $this->dataPageFactory->create(); - $this->dataObjectHelper->populateWithArray( - $pageData, - $pageModel->getData(), - \Magento\Cms\Api\Data\PageInterface::class - ); - $pages[] = $this->dataObjectProcessor->buildOutputDataArray( - $pageData, - \Magento\Cms\Api\Data\PageInterface::class - ); - } - /** @var Data\PageSearchResultsInterface $searchResults */ $searchResults = $this->searchResultsFactory->create(); $searchResults->setSearchCriteria($criteria); - $searchResults->setItems($pages); + $searchResults->setItems($collection->getItems()); $searchResults->setTotalCount($collection->getSize()); return $searchResults; } diff --git a/app/code/Magento/Cms/Test/Unit/Model/BlockRepositoryTest.php b/app/code/Magento/Cms/Test/Unit/Model/BlockRepositoryTest.php index dd43f4a0b228..6e4440ef97b2 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/BlockRepositoryTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/BlockRepositoryTest.php @@ -263,22 +263,8 @@ public function testGetList() ->willReturnSelf(); $this->blockSearchResult->expects($this->once()) ->method('setItems') - ->with(['someData']) + ->with([$this->block]) ->willReturnSelf(); - - $this->block->expects($this->once()) - ->method('getData') - ->willReturn(['data']); - - $this->dataHelper->expects($this->once()) - ->method('populateWithArray') - ->with($this->blockData, ['data'], \Magento\Cms\Api\Data\BlockInterface::class); - - $this->dataObjectProcessor->expects($this->once()) - ->method('buildOutputDataArray') - ->with($this->blockData, \Magento\Cms\Api\Data\BlockInterface::class) - ->willReturn('someData'); - $this->assertEquals($this->blockSearchResult, $this->repository->getList($criteria)); } } diff --git a/app/code/Magento/Cms/Test/Unit/Model/PageRepositoryTest.php b/app/code/Magento/Cms/Test/Unit/Model/PageRepositoryTest.php index 7d064c73b325..1bd742048c51 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/PageRepositoryTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/PageRepositoryTest.php @@ -261,22 +261,8 @@ public function testGetList() ->willReturnSelf(); $this->pageSearchResult->expects($this->once()) ->method('setItems') - ->with(['someData']) + ->with([$this->page]) ->willReturnSelf(); - - $this->page->expects($this->once()) - ->method('getData') - ->willReturn(['data']); - - $this->dataHelper->expects($this->once()) - ->method('populateWithArray') - ->with($this->pageData, ['data'], \Magento\Cms\Api\Data\PageInterface::class); - - $this->dataObjectProcessor->expects($this->once()) - ->method('buildOutputDataArray') - ->with($this->pageData, \Magento\Cms\Api\Data\PageInterface::class) - ->willReturn('someData'); - $this->assertEquals($this->pageSearchResult, $this->repository->getList($criteria)); } } diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php index b8770482f032..0bd28f0f78d9 100644 --- a/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php +++ b/app/code/Magento/Config/Console/Command/ConfigSet/LockProcessor.php @@ -8,6 +8,7 @@ use Magento\Config\App\Config\Type\System; use Magento\Config\Model\PreparedValueFactory; use Magento\Framework\App\Config\ConfigPathResolver; +use Magento\Framework\App\Config\Value; use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\File\ConfigFilePool; use Magento\Framework\Exception\CouldNotSaveException; @@ -79,19 +80,27 @@ public function process($path, $value, $scope, $scopeCode) $configPath = $this->configPathResolver->resolve($path, $scope, $scopeCode, System::CONFIG_TYPE); $backendModel = $this->preparedValueFactory->create($path, $value, $scope, $scopeCode); - /** - * Temporary solution until Magento introduce unified interface - * for storing system configuration into database and configuration files. - */ - $backendModel->validateBeforeSave(); - $backendModel->beforeSave(); + if ($backendModel instanceof Value) { + /** + * Temporary solution until Magento introduce unified interface + * for storing system configuration into database and configuration files. + */ + $backendModel->validateBeforeSave(); + $backendModel->beforeSave(); - $this->deploymentConfigWriter->saveConfig( - [ConfigFilePool::APP_ENV => $this->arrayManager->set($configPath, [], $backendModel->getValue())], - false - ); + $value = $backendModel->getValue(); - $backendModel->afterSave(); + $backendModel->afterSave(); + + /** + * Because FS does not support transactions, + * we'll write value just after all validations are triggered. + */ + $this->deploymentConfigWriter->saveConfig( + [ConfigFilePool::APP_ENV => $this->arrayManager->set($configPath, [], $value)], + false + ); + } } catch (\Exception $exception) { throw new CouldNotSaveException(__('%1', $exception->getMessage()), $exception); } diff --git a/app/code/Magento/Config/Model/Config/Backend/Image/Favicon.php b/app/code/Magento/Config/Model/Config/Backend/Image/Favicon.php index 960853778d5f..1412e0cd77c1 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Image/Favicon.php +++ b/app/code/Magento/Config/Model/Config/Backend/Image/Favicon.php @@ -45,6 +45,6 @@ protected function _addWhetherScopeInfo() */ protected function _getAllowedExtensions() { - return ['ico', 'png', 'gif', 'jpg', 'jpeg', 'apng', 'svg']; + return ['ico', 'png', 'gif', 'jpg', 'jpeg', 'apng']; } } diff --git a/app/code/Magento/Config/Model/Config/Backend/Image/Logo.php b/app/code/Magento/Config/Model/Config/Backend/Image/Logo.php index 908ae53af099..fc57287fb494 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Image/Logo.php +++ b/app/code/Magento/Config/Model/Config/Backend/Image/Logo.php @@ -45,6 +45,6 @@ protected function _addWhetherScopeInfo() */ protected function _getAllowedExtensions() { - return ['jpg', 'jpeg', 'gif', 'png', 'svg']; + return ['jpg', 'jpeg', 'gif', 'png']; } } diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php index e37214411ff5..62028eb78923 100644 --- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php +++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/LockProcessorTest.php @@ -206,10 +206,10 @@ public function testCustomException() ->willReturn($this->valueMock); $this->arrayManagerMock->expects($this->never()) ->method('set'); - $this->valueMock->expects($this->never()) + $this->valueMock->expects($this->once()) ->method('getValue'); $this->valueMock->expects($this->once()) - ->method('validateBeforeSave') + ->method('afterSave') ->willThrowException(new \Exception('Invalid values')); $this->deploymentConfigWriterMock->expects($this->never()) ->method('saveConfig'); diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Backend/Image/LogoTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Backend/Image/LogoTest.php index e68cff5d1280..28f35c233b87 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Backend/Image/LogoTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Backend/Image/LogoTest.php @@ -73,7 +73,7 @@ public function testBeforeSave() ->will($this->returnValue('/tmp/val')); $this->uploaderMock->expects($this->once()) ->method('setAllowedExtensions') - ->with($this->equalTo(['jpg', 'jpeg', 'gif', 'png', 'svg'])); + ->with($this->equalTo(['jpg', 'jpeg', 'gif', 'png'])); $this->model->beforeSave(); } } diff --git a/app/code/Magento/ConfigurableProduct/Model/AttributeOptionProvider.php b/app/code/Magento/ConfigurableProduct/Model/AttributeOptionProvider.php index d9dda323c56a..759691433b80 100644 --- a/app/code/Magento/ConfigurableProduct/Model/AttributeOptionProvider.php +++ b/app/code/Magento/ConfigurableProduct/Model/AttributeOptionProvider.php @@ -6,13 +6,15 @@ namespace Magento\ConfigurableProduct\Model; +use Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Framework\App\ScopeResolverInterface; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute; -use Magento\Framework\App\ScopeInterface; use Magento\Framework\DB\Select; -use Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionProvider; +/** + * Provider for retrieving configurable options. + */ class AttributeOptionProvider implements AttributeOptionProviderInterface { /** @@ -26,23 +28,23 @@ class AttributeOptionProvider implements AttributeOptionProviderInterface private $attributeResource; /** - * @var OptionProvider + * @var OptionSelectBuilderInterface */ - private $attributeOptionProvider; + private $optionSelectBuilder; /** * @param Attribute $attributeResource - * @param OptionProvider $attributeOptionProvider , - * @param ScopeResolverInterface $scopeResolver + * @param ScopeResolverInterface $scopeResolver, + * @param OptionSelectBuilderInterface $optionSelectBuilder */ public function __construct( Attribute $attributeResource, - OptionProvider $attributeOptionProvider, - ScopeResolverInterface $scopeResolver + ScopeResolverInterface $scopeResolver, + OptionSelectBuilderInterface $optionSelectBuilder ) { $this->attributeResource = $attributeResource; - $this->attributeOptionProvider = $attributeOptionProvider; $this->scopeResolver = $scopeResolver; + $this->optionSelectBuilder = $optionSelectBuilder; } /** @@ -50,8 +52,8 @@ public function __construct( */ public function getAttributeOptions(AbstractAttribute $superAttribute, $productId) { - $scope = $this->scopeResolver->getScope(); - $select = $this->getAttributeOptionsSelect($superAttribute, $productId, $scope); + $scope = $this->scopeResolver->getScope(); + $select = $this->optionSelectBuilder->getSelect($superAttribute, $productId, $scope); $data = $this->attributeResource->getConnection()->fetchAll($select); if ($superAttribute->getSourceModel()) { @@ -73,104 +75,4 @@ public function getAttributeOptions(AbstractAttribute $superAttribute, $productI return $data; } - - /** - * Get load options for attribute select - * - * @param AbstractAttribute $superAttribute - * @param int $productId - * @param ScopeInterface $scope - * @return Select - */ - private function getAttributeOptionsSelect(AbstractAttribute $superAttribute, $productId, ScopeInterface $scope) - { - $select = $this->attributeResource->getConnection()->select()->from( - ['super_attribute' => $this->attributeResource->getTable('catalog_product_super_attribute')], - [ - 'sku' => 'entity.sku', - 'product_id' => 'product_entity.entity_id', - 'attribute_code' => 'attribute.attribute_code', - 'value_index' => 'entity_value.value', - 'super_attribute_label' => 'attribute_label.value', - ] - )->joinInner( - ['product_entity' => $this->attributeResource->getTable('catalog_product_entity')], - "product_entity.{$this->attributeOptionProvider->getProductEntityLinkField()} = super_attribute.product_id", - [] - )->joinInner( - ['product_link' => $this->attributeResource->getTable('catalog_product_super_link')], - 'product_link.parent_id = super_attribute.product_id', - [] - )->joinInner( - ['attribute' => $this->attributeResource->getTable('eav_attribute')], - 'attribute.attribute_id = super_attribute.attribute_id', - [] - )->joinInner( - ['entity' => $this->attributeResource->getTable('catalog_product_entity')], - 'entity.entity_id = product_link.product_id', - [] - )->joinInner( - ['entity_value' => $superAttribute->getBackendTable()], - implode( - ' AND ', - [ - 'entity_value.attribute_id = super_attribute.attribute_id', - 'entity_value.store_id = 0', - "entity_value.{$this->attributeOptionProvider->getProductEntityLinkField()} = " - . "entity.{$this->attributeOptionProvider->getProductEntityLinkField()}", - ] - ), - [] - )->joinLeft( - ['attribute_label' => $this->attributeResource->getTable('catalog_product_super_attribute_label')], - implode( - ' AND ', - [ - 'super_attribute.product_super_attribute_id = attribute_label.product_super_attribute_id', - 'attribute_label.store_id = ' . \Magento\Store\Model\Store::DEFAULT_STORE_ID, - ] - ), - [] - )->where( - 'super_attribute.product_id = ?', - $productId - )->where( - 'attribute.attribute_id = ?', - $superAttribute->getAttributeId() - ); - - if (!$superAttribute->getSourceModel()) { - $select->columns( - [ - 'option_title' => $this->attributeResource->getConnection()->getIfNullSql( - 'option_value.value', - 'default_option_value.value' - ), - 'default_title' => 'default_option_value.value', - ] - )->joinLeft( - ['option_value' => $this->attributeResource->getTable('eav_attribute_option_value')], - implode( - ' AND ', - [ - 'option_value.option_id = entity_value.value', - 'option_value.store_id = ' . $scope->getId(), - ] - ), - [] - )->joinLeft( - ['default_option_value' => $this->attributeResource->getTable('eav_attribute_option_value')], - implode( - ' AND ', - [ - 'default_option_value.option_id = entity_value.value', - 'default_option_value.store_id = ' . \Magento\Store\Model\Store::DEFAULT_STORE_ID, - ] - ), - [] - ); - } - - return $select; - } } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php new file mode 100644 index 000000000000..c8e4af088a0f --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php @@ -0,0 +1,136 @@ +attributeResource = $attributeResource; + $this->attributeOptionProvider = $attributeOptionProvider; + } + + /** + * {@inheritdoc} + */ + public function getSelect(AbstractAttribute $superAttribute, int $productId, ScopeInterface $scope) + { + $select = $this->attributeResource->getConnection()->select()->from( + ['super_attribute' => $this->attributeResource->getTable('catalog_product_super_attribute')], + [ + 'sku' => 'entity.sku', + 'product_id' => 'product_entity.entity_id', + 'attribute_code' => 'attribute.attribute_code', + 'value_index' => 'entity_value.value', + 'super_attribute_label' => 'attribute_label.value', + ] + )->joinInner( + ['product_entity' => $this->attributeResource->getTable('catalog_product_entity')], + "product_entity.{$this->attributeOptionProvider->getProductEntityLinkField()} = super_attribute.product_id", + [] + )->joinInner( + ['product_link' => $this->attributeResource->getTable('catalog_product_super_link')], + 'product_link.parent_id = super_attribute.product_id', + [] + )->joinInner( + ['attribute' => $this->attributeResource->getTable('eav_attribute')], + 'attribute.attribute_id = super_attribute.attribute_id', + [] + )->joinInner( + ['entity' => $this->attributeResource->getTable('catalog_product_entity')], + 'entity.entity_id = product_link.product_id', + [] + )->joinInner( + ['entity_value' => $superAttribute->getBackendTable()], + implode( + ' AND ', + [ + 'entity_value.attribute_id = super_attribute.attribute_id', + 'entity_value.store_id = 0', + "entity_value.{$this->attributeOptionProvider->getProductEntityLinkField()} = " + . "entity.{$this->attributeOptionProvider->getProductEntityLinkField()}", + ] + ), + [] + )->joinLeft( + ['attribute_label' => $this->attributeResource->getTable('catalog_product_super_attribute_label')], + implode( + ' AND ', + [ + 'super_attribute.product_super_attribute_id = attribute_label.product_super_attribute_id', + 'attribute_label.store_id = ' . \Magento\Store\Model\Store::DEFAULT_STORE_ID, + ] + ), + [] + )->where( + 'super_attribute.product_id = ?', + $productId + )->where( + 'attribute.attribute_id = ?', + $superAttribute->getAttributeId() + ); + + if (!$superAttribute->getSourceModel()) { + $select->columns( + [ + 'option_title' => $this->attributeResource->getConnection()->getIfNullSql( + 'option_value.value', + 'default_option_value.value' + ), + 'default_title' => 'default_option_value.value', + ] + )->joinLeft( + ['option_value' => $this->attributeResource->getTable('eav_attribute_option_value')], + implode( + ' AND ', + [ + 'option_value.option_id = entity_value.value', + 'option_value.store_id = ' . $scope->getId(), + ] + ), + [] + )->joinLeft( + ['default_option_value' => $this->attributeResource->getTable('eav_attribute_option_value')], + implode( + ' AND ', + [ + 'default_option_value.option_id = entity_value.value', + 'default_option_value.store_id = ' . \Magento\Store\Model\Store::DEFAULT_STORE_ID, + ] + ), + [] + ); + } + + return $select; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilderInterface.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilderInterface.php new file mode 100644 index 000000000000..400d89b836e5 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilderInterface.php @@ -0,0 +1,26 @@ +stockStatusResource = $stockStatusResource; + } + + /** + * Add stock status filter to select. + * + * @param OptionSelectBuilderInterface $subject + * @param Select $select + * @return Select + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetSelect(OptionSelectBuilderInterface $subject, Select $select) + { + $select->joinInner( + ['stock' => $this->stockStatusResource->getMainTable()], + 'stock.product_id = entity.entity_id', + [] + )->where( + 'stock.stock_status = ?', + \Magento\CatalogInventory\Model\Stock\Status::STATUS_IN_STOCK + ); + + return $select; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php index 498a7cac77e5..727598160ea1 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php @@ -63,7 +63,7 @@ public function getProducts(ProductInterface $product) ); $this->linkedProductMap[$product->getId()] = $this->collectionFactory->create() - ->addAttributeToSelect(['price', 'special_price']) + ->addAttributeToSelect(['price', 'special_price', 'special_from_date', 'special_to_date']) ->addIdFilter($productIds) ->getItems(); } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/AttributeOptionProviderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/AttributeOptionProviderTest.php index 925f7d6f4225..8a2530ab58eb 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/AttributeOptionProviderTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/AttributeOptionProviderTest.php @@ -7,18 +7,15 @@ namespace Magento\ConfigurableProduct\Test\Unit\Model; use Magento\ConfigurableProduct\Model\AttributeOptionProvider; +use Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface; use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; -use Magento\Framework\DB\Select; +use Magento\Framework\App\ScopeInterface; use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\DB\Select; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; -use Magento\Framework\App\ScopeInterface; use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute; -use Magento\CatalogInventory\Api\StockStatusRepositoryInterface; -use Magento\CatalogInventory\Api\StockStatusCriteriaInterfaceFactory; -use Magento\CatalogInventory\Api\StockStatusCriteriaInterface; -use Magento\CatalogInventory\Api\Data\StockStatusCollectionInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -35,11 +32,6 @@ class AttributeOptionProviderTest extends \PHPUnit_Framework_TestCase */ private $objectManagerHelper; - /** - * @var \Magento\Framework\EntityManager\EntityMetadata|\PHPUnit_Framework_MockObject_MockObject - */ - private $metadataMock; - /** * @var ScopeResolverInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -71,70 +63,39 @@ class AttributeOptionProviderTest extends \PHPUnit_Framework_TestCase private $attributeResource; /** - * @var StockStatusRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $stockStatusRepository; - - /** - * @var StockStatusCriteriaInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $stockStatusCriteriaFactory; - - /** - * @var StockStatusCriteriaInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $stockStatusCriteriaInterface; - - /** - * @var StockStatusCollectionInterface|\PHPUnit_Framework_MockObject_MockObject + * @var OptionSelectBuilderInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $stockStatusCollection; + private $optionSelectBuilder; protected function setUp() { - $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) - ->setMethods(['select', 'fetchAll']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); $this->select = $this->getMockBuilder(Select::class) - ->setMethods(['from', 'joinInner', 'joinLeft', 'where', 'columns']) + ->setMethods([]) ->disableOriginalConstructor() ->getMock(); - $this->connectionMock->expects($this->any()) - ->method('select') - ->willReturn($this->select); - $this->metadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadata::class) - ->disableOriginalConstructor() - ->getMock(); - $this->scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->abstractAttribute = $this->getMockBuilder(AbstractAttribute::class) - ->setMethods(['getBackendTable', 'getAttributeId', 'getSourceModel', 'getSource']) + $this->connectionMock = $this->getMockBuilder(AdapterInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->scope = $this->getMockBuilder(ScopeInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->attributeResource = $this->getMockBuilder(Attribute::class) - ->setMethods(['getTable', 'getConnection']) - ->disableOriginalConstructor() - ->getMock(); - $this->attributeResource->expects($this->any()) - ->method('getConnection') - ->willReturn($this->connectionMock); - $this->stockStatusRepository = $this->getMockBuilder(StockStatusRepositoryInterface::class) + + $this->scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->stockStatusCriteriaFactory = $this->getMockBuilder(StockStatusCriteriaInterfaceFactory::class) - ->setMethods(['create']) + + $this->attributeResource = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->getMock(); - $this->stockStatusCriteriaInterface = $this->getMockBuilder(StockStatusCriteriaInterface::class) + + $this->optionSelectBuilder = $this->getMockBuilder(OptionSelectBuilderInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->stockStatusCollection = $this->getMockBuilder(StockStatusCollectionInterface::class) + + $this->abstractAttribute = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods(['getSourceModel', 'getSource']) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -143,87 +104,83 @@ protected function setUp() AttributeOptionProvider::class, [ 'attributeResource' => $this->attributeResource, - 'stockStatusRepository' => $this->stockStatusRepository, - 'stockStatusCriteriaFactory' => $this->stockStatusCriteriaFactory, 'scopeResolver' => $this->scopeResolver, + 'optionSelectBuilder' => $this->optionSelectBuilder, ] ); } /** * @param array $options - * @dataProvider testOptionsDataProvider + * @dataProvider getAttributeOptionsDataProvider */ public function testGetAttributeOptions(array $options) { - $this->scopeResolver->expects($this->any())->method('getScope')->willReturn($this->scope); - $this->scope->expects($this->any())->method('getId')->willReturn(123); - - $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); - $this->select->expects($this->exactly(1))->method('columns')->willReturnSelf(); - $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); - $this->select->expects($this->exactly(3))->method('joinLeft')->willReturnSelf(); - $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); - - $this->abstractAttribute->expects($this->any()) - ->method('getBackendTable') - ->willReturn('getBackendTable value'); - $this->abstractAttribute->expects($this->any()) - ->method('getAttributeId') - ->willReturn('getAttributeId value'); + $this->scopeResolver->expects($this->any()) + ->method('getScope') + ->willReturn($this->scope); + + $this->optionSelectBuilder->expects($this->any()) + ->method('getSelect') + ->with($this->abstractAttribute, 4, $this->scope) + ->willReturn($this->select); + + $this->attributeResource->expects($this->once()) + ->method('getConnection') + ->willReturn($this->connectionMock); $this->connectionMock->expects($this->once()) ->method('fetchAll') + ->with($this->select) ->willReturn($options); $this->assertEquals( $options, - $this->model->getAttributeOptions($this->abstractAttribute, 1) + $this->model->getAttributeOptions($this->abstractAttribute, 4) ); } /** * @param array $options - * @dataProvider testOptionsWithBackendModelDataProvider + * @dataProvider optionsWithBackendModelDataProvider */ public function testGetAttributeOptionsWithBackendModel(array $options) { - $this->scopeResolver->expects($this->any())->method('getScope')->willReturn($this->scope); - $this->scope->expects($this->any())->method('getId')->willReturn(123); - - $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); - $this->select->expects($this->exactly(0))->method('columns')->willReturnSelf(); - $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); - $this->select->expects($this->exactly(1))->method('joinLeft')->willReturnSelf(); - $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); + $this->scopeResolver->expects($this->any()) + ->method('getScope') + ->willReturn($this->scope); $source = $this->getMockBuilder(AbstractSource::class) ->disableOriginalConstructor() ->setMethods(['getAllOptions']) ->getMockForAbstractClass(); - $source->expects($this->any()) + $source->expects($this->once()) ->method('getAllOptions') ->willReturn([ ['value' => 13, 'label' => 'Option Value for index 13'], ['value' => 14, 'label' => 'Option Value for index 14'], ['value' => 15, 'label' => 'Option Value for index 15'] ]); - - $this->abstractAttribute->expects($this->atLeastOnce()) + + $this->abstractAttribute->expects($this->any()) ->method('getSource') ->willReturn($source); - $this->abstractAttribute->expects($this->any()) - ->method('getBackendTable') - ->willReturn('getBackendTable value'); - $this->abstractAttribute->expects($this->any()) + $this->abstractAttribute->expects($this->atLeastOnce()) ->method('getSourceModel') ->willReturn('getSourceModel value'); - $this->abstractAttribute->expects($this->any()) - ->method('getAttributeId') - ->willReturn('getAttributeId value'); + + $this->optionSelectBuilder->expects($this->any()) + ->method('getSelect') + ->with($this->abstractAttribute, 1, $this->scope) + ->willReturn($this->select); + + $this->attributeResource->expects($this->once()) + ->method('getConnection') + ->willReturn($this->connectionMock); $this->connectionMock->expects($this->once()) ->method('fetchAll') + ->with($this->select) ->willReturn($options); $this->assertEquals( @@ -235,7 +192,7 @@ public function testGetAttributeOptionsWithBackendModel(array $options) /** * @return array */ - public function testOptionsDataProvider() + public function getAttributeOptionsDataProvider() { return [ [ @@ -269,7 +226,7 @@ public function testOptionsDataProvider() /** * @return array */ - public function testOptionsWithBackendModelDataProvider() + public function optionsWithBackendModelDataProvider() { return [ [ diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php new file mode 100644 index 000000000000..1ccd0b08c291 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php @@ -0,0 +1,162 @@ +connectionMock = $this->getMockBuilder(AdapterInterface::class) + ->setMethods(['select', 'getIfNullSql']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->select = $this->getMockBuilder(Select::class) + ->setMethods(['from', 'joinInner', 'joinLeft', 'where', 'columns']) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionMock->expects($this->atLeastOnce()) + ->method('select', 'getIfNullSql') + ->willReturn($this->select); + + $this->attributeResourceMock = $this->getMockBuilder(Attribute::class) + ->setMethods(['getTable', 'getConnection']) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeResourceMock->expects($this->atLeastOnce()) + ->method('getConnection') + ->willReturn($this->connectionMock); + + $this->attributeOptionProviderMock = $this->getMockBuilder(OptionProvider::class) + ->setMethods(['getProductEntityLinkField']) + ->disableOriginalConstructor() + ->getMock(); + + $this->abstractAttributeMock = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods(['getBackendTable', 'getAttributeId', 'getSourceModel']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->scope = $this->getMockBuilder(ScopeInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->model = $this->objectManagerHelper->getObject( + OptionSelectBuilder::class, + [ + 'attributeResource' => $this->attributeResourceMock, + 'attributeOptionProvider' => $this->attributeOptionProviderMock, + ] + ); + } + + /** + * Test for method getSelect + */ + public function testGetSelect() + { + $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); + $this->select->expects($this->exactly(1))->method('columns')->willReturnSelf(); + $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); + $this->select->expects($this->exactly(3))->method('joinLeft')->willReturnSelf(); + $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); + + $this->abstractAttributeMock->expects($this->atLeastOnce()) + ->method('getAttributeId') + ->willReturn('getAttributeId value'); + $this->abstractAttributeMock->expects($this->atLeastOnce()) + ->method('getBackendTable') + ->willReturn('getMainTable value'); + + $this->scope->expects($this->any())->method('getId')->willReturn(123); + + $this->assertEquals( + $this->select, + $this->model->getSelect($this->abstractAttributeMock, 4, $this->scope) + ); + } + + /** + * Test for method getSelect with backend table + */ + public function testGetSelectWithBackendModel() + { + $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); + $this->select->expects($this->exactly(0))->method('columns')->willReturnSelf(); + $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); + $this->select->expects($this->exactly(1))->method('joinLeft')->willReturnSelf(); + $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); + + $this->abstractAttributeMock->expects($this->atLeastOnce()) + ->method('getAttributeId') + ->willReturn('getAttributeId value'); + $this->abstractAttributeMock->expects($this->atLeastOnce()) + ->method('getBackendTable') + ->willReturn('getMainTable value'); + $this->abstractAttributeMock->expects($this->atLeastOnce()) + ->method('getSourceModel') + ->willReturn('source model value'); + + $this->scope->expects($this->any())->method('getId')->willReturn(123); + + $this->assertEquals( + $this->select, + $this->model->getSelect($this->abstractAttributeMock, 4, $this->scope) + ); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilderTest.php new file mode 100644 index 000000000000..18550e7d26f9 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Model/ResourceModel/Attribute/InStockOptionSelectBuilderTest.php @@ -0,0 +1,87 @@ +stockStatusResourceMock = $this->getMockBuilder(Status::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->selectMock = $this->getMockBuilder(Select::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->optionSelectBuilderMock = $this->getMockBuilder(OptionSelectBuilder::class) + ->setMethods([]) + ->disableOriginalConstructor() + ->getMock(); + + $this->objectManagerHelper = new ObjectManager($this); + $this->model = $this->objectManagerHelper->getObject( + InStockOptionSelectBuilder::class, + [ + 'stockStatusResource' => $this->stockStatusResourceMock, + ] + ); + } + + /** + * Test for method afterGetSelect. + */ + public function testAfterGetSelect() + { + $this->stockStatusResourceMock->expects($this->once()) + ->method('getMainTable') + ->willReturn('stock_table_name'); + + $this->selectMock->expects($this->once()) + ->method('joinInner') + ->willReturnSelf(); + $this->selectMock->expects($this->once()) + ->method('where') + ->willReturnSelf(); + + $this->assertEquals( + $this->selectMock, + $this->model->afterGetSelect($this->optionSelectBuilderMock, $this->selectMock) + ); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/LowestPriceOptionsProviderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/LowestPriceOptionsProviderTest.php new file mode 100644 index 000000000000..65b80eceefc8 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/LowestPriceOptionsProviderTest.php @@ -0,0 +1,100 @@ +connection = $this + ->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->getMock(); + $this->resourceConnection = $this + ->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->setMethods(['getConnection']) + ->getMock(); + $this->resourceConnection->expects($this->once())->method('getConnection')->willReturn($this->connection); + $this->linkedProductSelectBuilder = $this + ->getMockBuilder(LinkedProductSelectBuilderInterface::class) + ->setMethods(['build']) + ->getMock(); + $this->productCollection = $this + ->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Collection::class) + ->disableOriginalConstructor() + ->setMethods(['addAttributeToSelect', 'addIdFilter', 'getItems']) + ->getMock(); + $this->collectionFactory = $this + ->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->collectionFactory->expects($this->once())->method('create')->willReturn($this->productCollection); + + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProvider::class, + [ + 'resourceConnection' => $this->resourceConnection, + 'linkedProductSelectBuilder' => $this->linkedProductSelectBuilder, + 'collectionFactory' => $this->collectionFactory, + ] + ); + } + + public function testGetProducts() + { + $productId = 1; + $linkedProducts = ['some', 'linked', 'products', 'dataobjects']; + $product = $this->getMockBuilder(ProductInterface::class)->disableOriginalConstructor()->getMock(); + $product->expects($this->any())->method('getId')->willReturn($productId); + $this->linkedProductSelectBuilder->expects($this->any())->method('build')->with($productId)->willReturn([]); + $this->productCollection + ->expects($this->once()) + ->method('addAttributeToSelect') + ->with(['price', 'special_price', 'special_from_date', 'special_to_date']) + ->willReturnSelf(); + $this->productCollection->expects($this->once())->method('addIdFilter')->willReturnSelf(); + $this->productCollection->expects($this->once())->method('getItems')->willReturn($linkedProducts); + + $this->assertEquals($linkedProducts, $this->model->getProducts($product)); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ConfigurablePriceTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ConfigurablePriceTest.php index 79102ddefbd6..794f421f99cb 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ConfigurablePriceTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Ui/DataProvider/Product/Form/Modifier/ConfigurablePriceTest.php @@ -15,13 +15,106 @@ class ConfigurablePriceTest extends AbstractModifierTest */ protected function createModel() { - return $this->objectManager->getObject(ConfigurablePriceModifier::class); + return $this->objectManager->getObject(ConfigurablePriceModifier::class, ['locator' => $this->locatorMock]); } - public function testModifyMeta() + /** + * @param array $metaInput + * @param array $metaOutput + * @dataProvider metaDataProvider + */ + public function testModifyMeta($metaInput, $metaOutput) { - $meta = ['initial' => 'meta']; + $this->productMock->expects($this->any()) + ->method('getTypeId') + ->willReturn(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE); + + $metaResult = $this->getModel()->modifyMeta($metaInput); + $this->assertEquals($metaResult, $metaOutput); + } - $this->assertArrayHasKey('initial', $this->getModel()->modifyMeta($meta)); + /** + * @return array + */ + public function metaDataProvider() + { + return [ + [ + 'metaInput' => [ + 'pruduct-details' => [ + 'children' => [ + 'container_price' => [ + 'children' => [ + 'advanced_pricing_button' => [ + 'arguments' => [] + ] + ] + ] + ] + ] + ], + 'metaOutput' => [ + 'pruduct-details' => [ + 'children' => [ + 'container_price' => [ + 'children' => [ + 'advanced_pricing_button' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'visible' => 0, + 'disabled' => 1, + 'componentType' => 'container' + ], + ], + ], + ], + 'price' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'component' => + 'Magento_ConfigurableProduct/js/components/price-configurable' + ], + ], + ], + ], + ], + ], + ], + ] + ] + ], [ + 'metaInput' => [ + 'pruduct-details' => [ + 'children' => [ + 'container_price' => [ + 'children' => [] + ] + ] + ] + ], + 'metaOutput' => [ + 'pruduct-details' => [ + 'children' => [ + 'container_price' => [ + 'children' => [ + 'price' => [ + 'arguments' => [ + 'data' => [ + 'config' => [ + 'component' => + 'Magento_ConfigurableProduct/js/components/price-configurable' + ] + ] + ] + ] + ] + ] + ] + ] + ] + ] + ]; } } diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php index f294cb14da4f..e3e1ef2ff87b 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePrice.php @@ -49,9 +49,10 @@ public function modifyData(array $data) */ public function modifyMeta(array $meta) { - if ($groupCode = $this->getGroupCodeByField($meta, ProductAttributeInterface::CODE_PRICE) - ?: $this->getGroupCodeByField($meta, self::CODE_GROUP_PRICE) - ) { + $groupCode = $this->getGroupCodeByField($meta, ProductAttributeInterface::CODE_PRICE) + ?: $this->getGroupCodeByField($meta, self::CODE_GROUP_PRICE); + + if ($groupCode && !empty($meta[$groupCode]['children'][self::CODE_GROUP_PRICE])) { if (!empty($meta[$groupCode]['children'][self::CODE_GROUP_PRICE])) { $meta[$groupCode]['children'][self::CODE_GROUP_PRICE] = array_replace_recursive( $meta[$groupCode]['children'][self::CODE_GROUP_PRICE], @@ -71,7 +72,9 @@ public function modifyMeta(array $meta) ] ); } - if (!empty($meta[$groupCode]['children'][self::CODE_GROUP_PRICE])) { + if ( + !empty($meta[$groupCode]['children'][self::CODE_GROUP_PRICE]['children'][self::$advancedPricingButton]) + ) { $productTypeId = $this->locator->getProduct()->getTypeId(); $visibilityConfig = ($productTypeId === ConfigurableType::TYPE_CODE) ? ['visible' => 0, 'disabled' => 1] @@ -81,6 +84,8 @@ public function modifyMeta(array $meta) . ConfigurablePanel::CONFIGURABLE_MATRIX . ':isEmpty', ] ]; + $config = $visibilityConfig; + $config['componentType'] = 'container'; $meta[$groupCode]['children'][self::CODE_GROUP_PRICE] = array_replace_recursive( $meta[$groupCode]['children'][self::CODE_GROUP_PRICE], [ @@ -88,10 +93,7 @@ public function modifyMeta(array $meta) self::$advancedPricingButton => [ 'arguments' => [ 'data' => [ - 'config' => [ - 'componentType' => 'container', - $visibilityConfig - ], + 'config' => $config, ], ], ], diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 496d85310e81..9e913a3592cb 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -15,6 +15,7 @@ + diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml index 99ab8ee9050c..592b0292c98a 100644 --- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml @@ -14,4 +14,7 @@ + + + diff --git a/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php b/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php index 1e494d59046e..a72e97138119 100644 --- a/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php +++ b/app/code/Magento/Cron/Observer/ProcessCronQueueObserver.php @@ -97,9 +97,9 @@ class ProcessCronQueueObserver implements ObserverInterface protected $_shell; /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var \Magento\Framework\Stdlib\DateTime\DateTime */ - protected $timezone; + protected $dateTime; /** * @var \Symfony\Component\Process\PhpExecutableFinder @@ -124,7 +124,7 @@ class ProcessCronQueueObserver implements ObserverInterface * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\App\Console\Request $request * @param \Magento\Framework\ShellInterface $shell - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone + * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime * @param \Magento\Framework\Process\PhpExecutableFinderFactory $phpExecutableFinderFactory * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\App\State $state @@ -138,7 +138,7 @@ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Framework\App\Console\Request $request, \Magento\Framework\ShellInterface $shell, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $timezone, + \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, \Magento\Framework\Process\PhpExecutableFinderFactory $phpExecutableFinderFactory, \Psr\Log\LoggerInterface $logger, \Magento\Framework\App\State $state @@ -150,7 +150,7 @@ public function __construct( $this->_scopeConfig = $scopeConfig; $this->_request = $request; $this->_shell = $shell; - $this->timezone = $timezone; + $this->dateTime = $dateTime; $this->phpExecutableFinder = $phpExecutableFinderFactory->create(); $this->logger = $logger; $this->state = $state; @@ -170,7 +170,7 @@ public function __construct( public function execute(\Magento\Framework\Event\Observer $observer) { $pendingJobs = $this->_getPendingSchedules(); - $currentTime = $this->timezone->scopeTimeStamp(); + $currentTime = $this->dateTime->gmtTimestamp(); $jobGroupsRoot = $this->_config->getJobs(); $phpPath = $this->phpExecutableFinder->find() ?: 'php'; @@ -274,7 +274,7 @@ protected function _runJob($scheduledTime, $currentTime, $jobConfig, $schedule, ); } - $schedule->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', $this->timezone->scopeTimeStamp()))->save(); + $schedule->setExecutedAt(strftime('%Y-%m-%d %H:%M:%S', $this->dateTime->gmtTimestamp()))->save(); try { call_user_func_array($callback, [$schedule]); @@ -285,7 +285,7 @@ protected function _runJob($scheduledTime, $currentTime, $jobConfig, $schedule, $schedule->setStatus(Schedule::STATUS_SUCCESS)->setFinishedAt(strftime( '%Y-%m-%d %H:%M:%S', - $this->timezone->scopeTimeStamp() + $this->dateTime->gmtTimestamp() )); } @@ -322,7 +322,7 @@ protected function _generate($groupId) \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); $schedulePeriod = $rawSchedulePeriod * self::SECONDS_IN_MINUTE; - if ($lastRun > $this->timezone->scopeTimeStamp() - $schedulePeriod) { + if ($lastRun > $this->dateTime->gmtTimestamp() - $schedulePeriod) { return $this; } @@ -343,7 +343,7 @@ protected function _generate($groupId) * save time schedules generation was ran with no expiration */ $this->_cache->save( - $this->timezone->scopeTimeStamp(), + $this->dateTime->gmtTimestamp(), self::CACHE_KEY_LAST_SCHEDULE_GENERATE_AT . $groupId, ['crontab'], null @@ -398,7 +398,7 @@ protected function _cleanup($groupId) 'system/cron/' . $groupId . '/' . self::XML_PATH_HISTORY_CLEANUP_EVERY, \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); - if ($lastCleanup > $this->timezone->scopeTimeStamp() - $historyCleanUp * self::SECONDS_IN_MINUTE) { + if ($lastCleanup > $this->dateTime->gmtTimestamp() - $historyCleanUp * self::SECONDS_IN_MINUTE) { return $this; } @@ -431,7 +431,7 @@ protected function _cleanup($groupId) Schedule::STATUS_ERROR => $historyFailure * self::SECONDS_IN_MINUTE, ]; - $now = $this->timezone->scopeTimeStamp(); + $now = $this->dateTime->gmtTimestamp(); /** @var Schedule $record */ foreach ($history as $record) { $checkTime = $record->getExecutedAt() ? strtotime($record->getExecutedAt()) : @@ -443,7 +443,7 @@ protected function _cleanup($groupId) // save time history cleanup was ran with no expiration $this->_cache->save( - $this->timezone->scopeTimeStamp(), + $this->dateTime->gmtTimestamp(), self::CACHE_KEY_LAST_HISTORY_CLEANUP_AT . $groupId, ['crontab'], null @@ -475,7 +475,7 @@ protected function getConfigSchedule($jobConfig) */ protected function saveSchedule($jobCode, $cronExpression, $timeInterval, $exists) { - $currentTime = $this->timezone->scopeTimeStamp(); + $currentTime = $this->dateTime->gmtTimestamp(); $timeAhead = $currentTime + $timeInterval; for ($time = $currentTime; $time < $timeAhead; $time += self::SECONDS_IN_MINUTE) { $ts = strftime('%Y-%m-%d %H:%M:00', $time); @@ -503,7 +503,7 @@ protected function generateSchedule($jobCode, $cronExpression, $time) ->setCronExpr($cronExpression) ->setJobCode($jobCode) ->setStatus(Schedule::STATUS_PENDING) - ->setCreatedAt(strftime('%Y-%m-%d %H:%M:%S', $this->timezone->scopeTimeStamp())) + ->setCreatedAt(strftime('%Y-%m-%d %H:%M:%S', $this->dateTime->gmtTimestamp())) ->setScheduledAt(strftime('%Y-%m-%d %H:%M', $time)); return $schedule; diff --git a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php index fadd9e52a7fe..7dfc037eb473 100644 --- a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php +++ b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php @@ -64,9 +64,9 @@ class ProcessCronQueueObserverTest extends \PHPUnit_Framework_TestCase protected $_cronGroupConfig; /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var \Magento\Framework\Stdlib\DateTime\DateTime */ - protected $timezone; + protected $dateTimeMock; /** * @var \Magento\Framework\Event\Observer @@ -126,8 +126,10 @@ protected function setUp() $this->observer = $this->getMock(\Magento\Framework\Event\Observer::class, [], [], '', false); - $this->timezone = $this->getMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); - $this->timezone->expects($this->any())->method('scopeTimeStamp')->will($this->returnValue(time())); + $this->dateTimeMock = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\DateTime::class) + ->disableOriginalConstructor() + ->getMock(); + $this->dateTimeMock->expects($this->any())->method('gmtTimestamp')->will($this->returnValue(time())); $phpExecutableFinder = $this->getMock(\Symfony\Component\Process\PhpExecutableFinder::class, [], [], '', false); $phpExecutableFinder->expects($this->any())->method('find')->willReturn('php'); @@ -148,7 +150,7 @@ protected function setUp() $this->_scopeConfig, $this->_request, $this->_shell, - $this->timezone, + $this->dateTimeMock, $phpExecutableFinderFactory, $this->loggerMock, $this->appStateMock diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 340d547199e2..98c89fdd6958 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -335,7 +335,7 @@ public function updateData($customer) $customAttributes = $customer->getCustomAttributes(); if ($customAttributes !== null) { foreach ($customAttributes as $attribute) { - $this->setDataUsingMethod($attribute->getAttributeCode(), $attribute->getValue()); + $this->setData($attribute->getAttributeCode(), $attribute->getValue()); } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php index f0a06c28193e..5e80f48ea832 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php @@ -58,6 +58,11 @@ class CustomerTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Customer\Model\ResourceModel\Customer|\PHPUnit_Framework_MockObject_MockObject */ protected $resourceMock; + /** + * @var \Magento\Framework\Reflection\DataObjectProcessor|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataObjectProcessor; + protected function setUp() { $this->_website = $this->getMock(\Magento\Store\Model\Website::class, [], [], '', false); @@ -102,6 +107,15 @@ protected function setUp() false, false ); + + $this->dataObjectProcessor = $this->getMock( + \Magento\Framework\Reflection\DataObjectProcessor::class, + ['buildOutputDataArray'], + [], + '', + false + ); + $this->resourceMock->expects($this->any()) ->method('getIdFieldName') ->will($this->returnValue('id')); @@ -119,6 +133,7 @@ protected function setUp() 'attributeFactory' => $this->attributeFactoryMock, 'registry' => $this->registryMock, 'resource' => $this->resourceMock, + 'dataObjectProcessor' => $this->dataObjectProcessor ] ); } @@ -271,4 +286,65 @@ public function dataProviderIsConfirmationRequired() [1, null, 'test2@example.com', true], ]; } + + public function testUpdateData() + { + $customerDataAttributes = [ + 'attribute' => 'attribute', + 'test1' => 'test1', + 'test33' => 'test33', + ]; + + $customer = $this->getMock( + \Magento\Customer\Model\Data\Customer::class, + [ + 'getCustomAttributes', + 'getId', + ], + [], + '', + false + ); + + $attribute = $this->getMock( + \Magento\Framework\Api\AttributeValue::class, + [ + 'getAttributeCode', + 'getValue', + ], + [], + '', + false + ); + + $this->dataObjectProcessor->expects($this->once()) + ->method('buildOutputDataArray') + ->withConsecutive( + [$customer, \Magento\Customer\Api\Data\CustomerInterface::class] + )->willReturn($customerDataAttributes); + + $attribute->expects($this->exactly(3)) + ->method('getAttributeCode') + ->willReturn('test33'); + + $attribute->expects($this->exactly(2)) + ->method('getValue') + ->willReturn('test33'); + + $customer->expects($this->once()) + ->method('getCustomAttributes') + ->willReturn([$attribute->getAttributeCode() => $attribute]); + + $this->_model->updateData($customer); + + foreach ($customerDataAttributes as $key => $value) { + $expectedResult[strtolower(trim(preg_replace('/([A-Z]|[0-9]+)/', "_$1", $key), '_'))] = $value; + } + + $expectedResult[$attribute->getAttributeCode()] = $attribute->getValue(); + $expectedResult['attribute_set_id'] = + \Magento\Customer\Api\CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER; + + $this->assertEquals($this->_model->getData(), $expectedResult); + } } diff --git a/app/code/Magento/Deploy/Console/DeployStaticOptions.php b/app/code/Magento/Deploy/Console/DeployStaticOptions.php index 7bf522f7ca35..9a73dd5d65fc 100644 --- a/app/code/Magento/Deploy/Console/DeployStaticOptions.php +++ b/app/code/Magento/Deploy/Console/DeployStaticOptions.php @@ -131,6 +131,11 @@ class DeployStaticOptions */ const CONTENT_VERSION = 'content-version'; + /** + * Key for refresh content version only mode + */ + const REFRESH_CONTENT_VERSION_ONLY = 'refresh-content-version-only'; + /** * Deploy static command options list * @@ -225,6 +230,13 @@ private function getBasicOptions() 'Custom version of static content can be used if running deployment on multiple nodes ' . 'to ensure that static content version is identical and caching works properly.' ), + new InputOption( + self::REFRESH_CONTENT_VERSION_ONLY, + null, + InputOption::VALUE_NONE, + 'Refreshing the version of static content only can be used to refresh static content ' + . 'in browser cache and CDN cache.' + ), new InputArgument( self::LANGUAGES_ARGUMENT, InputArgument::IS_ARRAY, diff --git a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php index d9f231c4cee3..8a464ca4bc62 100644 --- a/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php +++ b/app/code/Magento/Deploy/Package/Processor/PreProcessor/Less.php @@ -127,23 +127,34 @@ private function hasOverrides(PackageFile $parentFile, Package $package) $parentFile->getPackage()->getPath(), $parentFile->getExtension() ); - $parentFiles = $this->collectFileMap($parentFile->getFileName(), $map); - $currentPackageLessFiles = $package->getFilesByType('less'); - $currentPackageCssFiles = $package->getFilesByType('css'); /** @var PackageFile[] $currentPackageFiles */ - $currentPackageFiles = array_merge($currentPackageLessFiles, $currentPackageCssFiles); + $currentPackageFiles = array_merge($package->getFilesByType('less'), $package->getFilesByType('css')); foreach ($currentPackageFiles as $file) { - if (in_array($file->getDeployedFileName(), $parentFiles)) { + if ($this->inParentFiles($file->getDeployedFileName(), $parentFile->getFileName(), $map)) { return true; } } + return false; + } - $intersections = array_intersect($parentFiles, array_keys($currentPackageFiles)); - if ($intersections) { - return true; + /** + * @param string $fileName + * @param string $parentFile + * @param array $map + * @return bool + */ + private function inParentFiles($fileName, $parentFile, $map) + { + if (isset($map[$parentFile])) { + if (in_array($fileName, $map[$parentFile])) { + return true; + } else { + foreach ($map[$parentFile] as $pFile) { + return $this->inParentFiles($fileName, $pFile, $map); + } + } } - return false; } @@ -186,25 +197,6 @@ private function buildMap($filePath, $packagePath, $contentType) return $this->map; } - /** - * Flatten map tree into simple array - * - * Original map file information structure in form of tree, - * and to have checking of overridden files simpler we need to flatten that tree - * - * @param string $fileName - * @param array $map - * @return array - */ - private function collectFileMap($fileName, array $map) - { - $result = isset($map[$fileName]) ? $map[$fileName] : []; - foreach ($result as $fName) { - $result = array_merge($result, $this->collectFileMap($fName, $map)); - } - return array_unique($result); - } - /** * Return normalized path * diff --git a/app/code/Magento/Deploy/Process/Queue.php b/app/code/Magento/Deploy/Process/Queue.php index 31d662c874ac..671e47fe2d7d 100644 --- a/app/code/Magento/Deploy/Process/Queue.php +++ b/app/code/Magento/Deploy/Process/Queue.php @@ -187,24 +187,15 @@ private function assertAndExecute($name, array & $packages, array $packageJob) { /** @var Package $package */ $package = $packageJob['package']; - $parentPackagesDeployed = true; if ($package->getParent() && $package->getParent() !== $package) { - if (!$this->isDeployed($package->getParent())) { - $parentPackagesDeployed = false; - } else { - $dependencies = $packageJob['dependencies']; - foreach ($dependencies as $parentPackage) { - if (!$this->isDeployed($parentPackage)) { - $parentPackagesDeployed = false; - break; - } + foreach ($packageJob['dependencies'] as $dependencyName => $dependency) { + if (!$this->isDeployed($dependency)) { + $this->assertAndExecute($dependencyName, $packages, $packages[$dependencyName]); } } } - if ( - $parentPackagesDeployed - && ($this->maxProcesses < 2 || (count($this->inProgress) < $this->maxProcesses)) - ) { + if (!$this->isDeployed($package) + && ($this->maxProcesses < 2 || (count($this->inProgress) < $this->maxProcesses))) { unset($packages[$name]); $this->execute($package); } diff --git a/app/code/Magento/Deploy/Service/DeployRequireJsConfig.php b/app/code/Magento/Deploy/Service/DeployRequireJsConfig.php index c54f972485ef..966f127c6ba6 100644 --- a/app/code/Magento/Deploy/Service/DeployRequireJsConfig.php +++ b/app/code/Magento/Deploy/Service/DeployRequireJsConfig.php @@ -5,6 +5,8 @@ */ namespace Magento\Deploy\Service; +use Magento\Framework\Locale\ResolverInterfaceFactory; +use Magento\Framework\Locale\ResolverInterface; use Magento\RequireJs\Model\FileManagerFactory; use Magento\Framework\View\DesignInterfaceFactory; use Magento\Framework\View\Design\Theme\ListInterface; @@ -46,6 +48,11 @@ class DeployRequireJsConfig */ private $requireJsConfigFactory; + /** + * @var ResolverInterfaceFactory + */ + private $localeFactory; + /** * DeployRequireJsConfig constructor * @@ -54,31 +61,40 @@ class DeployRequireJsConfig * @param RepositoryFactory $assetRepoFactory * @param FileManagerFactory $fileManagerFactory * @param ConfigFactory $requireJsConfigFactory + * @param ResolverInterfaceFactory $localeFactory */ public function __construct( ListInterface $themeList, DesignInterfaceFactory $designFactory, RepositoryFactory $assetRepoFactory, FileManagerFactory $fileManagerFactory, - ConfigFactory $requireJsConfigFactory + ConfigFactory $requireJsConfigFactory, + ResolverInterfaceFactory $localeFactory ) { $this->themeList = $themeList; $this->designFactory = $designFactory; $this->assetRepoFactory = $assetRepoFactory; $this->fileManagerFactory = $fileManagerFactory; $this->requireJsConfigFactory = $requireJsConfigFactory; + $this->localeFactory = $localeFactory; } /** * @param string $areaCode * @param string $themePath + * @param string $localeCode * @return bool true on success */ - public function deploy($areaCode, $themePath) + public function deploy($areaCode, $themePath, $localeCode) { /** @var \Magento\Framework\View\Design\ThemeInterface $theme */ $theme = $this->themeList->getThemeByFullPath($areaCode . '/' . $themePath); + /** @var \Magento\Theme\Model\View\Design $design */ $design = $this->designFactory->create()->setDesignTheme($theme, $areaCode); + /** @var ResolverInterface $locale */ + $locale = $this->localeFactory->create(); + $locale->setLocale($localeCode); + $design->setLocale($locale); $assetRepo = $this->assetRepoFactory->create(['design' => $design]); /** @var \Magento\RequireJs\Model\FileManager $fileManager */ diff --git a/app/code/Magento/Deploy/Service/DeployStaticContent.php b/app/code/Magento/Deploy/Service/DeployStaticContent.php index 5f087ca6bb7f..66ec6e7418af 100644 --- a/app/code/Magento/Deploy/Service/DeployStaticContent.php +++ b/app/code/Magento/Deploy/Service/DeployStaticContent.php @@ -75,6 +75,16 @@ public function __construct( */ public function deploy(array $options) { + $version = !empty($options[Options::CONTENT_VERSION]) && is_string($options[Options::CONTENT_VERSION]) + ? $options[Options::CONTENT_VERSION] + : (new \DateTime())->getTimestamp(); + $this->versionStorage->save($version); + + if ($this->isRefreshContentVersionOnly($options)) { + $this->logger->warning("New content version: " . $version); + return; + } + $queue = $this->queueFactory->create( [ 'logger' => $this->logger, @@ -96,11 +106,6 @@ public function deploy(array $options) ] ); - $version = !empty($options[Options::CONTENT_VERSION]) && is_string($options[Options::CONTENT_VERSION]) - ? $options[Options::CONTENT_VERSION] - : (new \DateTime())->getTimestamp(); - $this->versionStorage->save($version); - $packages = $deployStrategy->deploy($options); if ($options[Options::NO_JAVASCRIPT] !== true) { @@ -115,7 +120,7 @@ public function deploy(array $options) ]); foreach ($packages as $package) { if (!$package->isVirtual()) { - $deployRjsConfig->deploy($package->getArea(), $package->getTheme()); + $deployRjsConfig->deploy($package->getArea(), $package->getTheme(), $package->getLocale()); $deployI18n->deploy($package->getArea(), $package->getTheme(), $package->getLocale()); $deployBundle->deploy($package->getArea(), $package->getTheme(), $package->getLocale()); } @@ -135,4 +140,14 @@ private function getProcessesAmount(array $options) { return isset($options[Options::JOBS_AMOUNT]) ? (int)$options[Options::JOBS_AMOUNT] : 0; } + + /** + * @param array $options + * @return bool + */ + private function isRefreshContentVersionOnly(array $options) + { + return isset($options[Options::REFRESH_CONTENT_VERSION_ONLY]) + && $options[Options::REFRESH_CONTENT_VERSION_ONLY]; + } } diff --git a/app/code/Magento/Deploy/Test/Unit/Process/QueueTest.php b/app/code/Magento/Deploy/Test/Unit/Process/QueueTest.php index a657a3a058a8..e97af44545a4 100644 --- a/app/code/Magento/Deploy/Test/Unit/Process/QueueTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Process/QueueTest.php @@ -113,8 +113,8 @@ public function testAdd() public function testProcess() { $package = $this->getMock(Package::class, [], [], '', false); - $package->expects($this->any())->method('getState')->willReturn(1); - $package->expects($this->once())->method('getParent')->willReturn(null); + $package->expects($this->any())->method('getState')->willReturn(0); + $package->expects($this->exactly(2))->method('getParent')->willReturn(true); $package->expects($this->any())->method('getArea')->willReturn('area'); $package->expects($this->any())->method('getPath')->willReturn('path'); $package->expects($this->any())->method('getFiles')->willReturn([]); diff --git a/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php b/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php index 04bfea068cab..8ac828cb195d 100644 --- a/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php @@ -114,14 +114,18 @@ protected function setUp() public function testDeploy($options, $expectedContentVersion) { $package = $this->getMock(Package::class, [], [], '', false); - $package->expects($this->exactly(1))->method('isVirtual')->willReturn(false); - $package->expects($this->exactly(3))->method('getArea')->willReturn('area'); - $package->expects($this->exactly(3))->method('getTheme')->willReturn('theme'); - $package->expects($this->exactly(2))->method('getLocale')->willReturn('locale'); - - $packages = [ - 'package' => $package - ]; + if ($options['refresh-content-version-only']) { + $package->expects($this->never())->method('isVirtual'); + $package->expects($this->never())->method('getArea'); + $package->expects($this->never())->method('getTheme'); + $package->expects($this->never())->method('getLocale'); + } else { + $package->expects($this->exactly(1))->method('isVirtual')->willReturn(false); + $package->expects($this->exactly(3))->method('getArea')->willReturn('area'); + $package->expects($this->exactly(3))->method('getTheme')->willReturn('theme'); + $package->expects($this->exactly(3))->method('getLocale')->willReturn('locale'); + } + $packages = ['package' => $package]; if ($expectedContentVersion) { $this->versionStorage->expects($this->once())->method('save')->with($expectedContentVersion); @@ -132,20 +136,27 @@ public function testDeploy($options, $expectedContentVersion) $queue = $this->getMockBuilder(Queue::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->queueFactory->expects($this->once())->method('create')->willReturn($queue); + if ($options['refresh-content-version-only']) { + $this->queueFactory->expects($this->never())->method('create'); + } else { + $this->queueFactory->expects($this->once())->method('create')->willReturn($queue); + } $strategy = $this->getMockBuilder(CompactDeploy::class) ->setMethods(['deploy']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $strategy->expects($this->once())->method('deploy') - ->with($options) - ->willReturn($packages); - $this->deployStrategyFactory->expects($this->once()) - ->method('create') - ->with('compact', ['queue' => $queue]) - ->willReturn($strategy); - + if ($options['refresh-content-version-only']) { + $strategy->expects($this->never())->method('deploy'); + } else { + $strategy->expects($this->once())->method('deploy') + ->with($options) + ->willReturn($packages); + $this->deployStrategyFactory->expects($this->once()) + ->method('create') + ->with('compact', ['queue' => $queue]) + ->willReturn($strategy); + } $deployPackageService = $this->getMockBuilder(DeployPackage::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -166,34 +177,32 @@ public function testDeploy($options, $expectedContentVersion) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->objectManager->expects($this->exactly(4)) - ->method('create') - ->withConsecutive( - [DeployPackage::class, ['logger' => $this->logger]], - [DeployRequireJsConfig::class, ['logger' => $this->logger]], - [DeployTranslationsDictionary::class, ['logger' => $this->logger]], - [Bundle::class, ['logger' => $this->logger]] - ) - ->willReturnOnConsecutiveCalls( - $deployPackageService, - $deployRjsConfig, - $deployI18n, - $deployBundle - ); - - $this->objectManager->expects($this->exactly(1)) - ->method('get') - ->withConsecutive( - [MinifyTemplates::class] - ) - ->willReturnOnConsecutiveCalls( - $minifyTemplates - ); - - $this->assertEquals( - null, - $this->service->deploy($options) - ); + if ($options['refresh-content-version-only']) { + $this->objectManager->expects($this->never())->method('create'); + $this->objectManager->expects($this->never())->method('get'); + } else { + $this->objectManager->expects($this->exactly(4)) + ->method('create') + ->withConsecutive( + [DeployPackage::class, ['logger' => $this->logger]], + [DeployRequireJsConfig::class, ['logger' => $this->logger]], + [DeployTranslationsDictionary::class, ['logger' => $this->logger]], + [Bundle::class, ['logger' => $this->logger]] + ) + ->willReturnOnConsecutiveCalls( + $deployPackageService, + $deployRjsConfig, + $deployI18n, + $deployBundle + ); + + $this->objectManager->expects($this->exactly(1)) + ->method('get') + ->withConsecutive([MinifyTemplates::class]) + ->willReturnOnConsecutiveCalls($minifyTemplates); + } + + $this->assertEquals(null, $this->service->deploy($options)); } public function deployDataProvider() @@ -203,7 +212,8 @@ public function deployDataProvider() [ 'strategy' => 'compact', 'no-javascript' => false, - 'no-html-minify' => false + 'no-html-minify' => false, + 'refresh-content-version-only' => false, ], null // content version value should not be asserted in this case ], @@ -212,9 +222,17 @@ public function deployDataProvider() 'strategy' => 'compact', 'no-javascript' => false, 'no-html-minify' => false, + 'refresh-content-version-only' => false, 'content-version' => '123456', ], '123456' + ], + [ + [ + 'refresh-content-version-only' => true, + 'content-version' => '654321', + ], + '654321' ] ]; } diff --git a/app/code/Magento/Developer/Console/Command/TemplateHintsDisableCommand.php b/app/code/Magento/Developer/Console/Command/TemplateHintsDisableCommand.php new file mode 100644 index 000000000000..dbecc673c1b8 --- /dev/null +++ b/app/code/Magento/Developer/Console/Command/TemplateHintsDisableCommand.php @@ -0,0 +1,62 @@ +resourceConfig = $resourceConfig; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName(self::COMMAND_NAME) + ->setDescription('Disable frontend template hints. A cache flush might be required.'); + + parent::configure(); + } + + /** + * {@inheritdoc} + * @throws \InvalidArgumentException + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->resourceConfig->saveConfig('dev/debug/template_hints_storefront', 0, 'default', 0); + $output->writeln("". self::SUCCESS_MESSAGE . ""); + } +} diff --git a/app/code/Magento/Developer/Console/Command/TemplateHintsEnableCommand.php b/app/code/Magento/Developer/Console/Command/TemplateHintsEnableCommand.php new file mode 100644 index 000000000000..b1813686c565 --- /dev/null +++ b/app/code/Magento/Developer/Console/Command/TemplateHintsEnableCommand.php @@ -0,0 +1,63 @@ +resourceConfig = $resourceConfig; + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName(self::COMMAND_NAME) + ->setDescription('Enable frontend template hints. A cache flush might be required.'); + + parent::configure(); + } + + /** + * {@inheritdoc} + * @throws \InvalidArgumentException + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->resourceConfig->saveConfig('dev/debug/template_hints_storefront', 1, 'default', 0); + $output->writeln("". self::SUCCESS_MESSAGE . ""); + } +} diff --git a/app/code/Magento/Developer/etc/di.xml b/app/code/Magento/Developer/etc/di.xml index 6b9bf23b4872..ca35c38a31b6 100644 --- a/app/code/Magento/Developer/etc/di.xml +++ b/app/code/Magento/Developer/etc/di.xml @@ -100,6 +100,8 @@ Magento\Developer\Console\Command\DiInfoCommand Magento\Developer\Console\Command\QueryLogEnableCommand Magento\Developer\Console\Command\QueryLogDisableCommand + Magento\Developer\Console\Command\TemplateHintsDisableCommand + Magento\Developer\Console\Command\TemplateHintsEnableCommand diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php index 32b72023340b..8f1324195b38 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php @@ -13,7 +13,7 @@ use Magento\Framework\App\CacheInterface; use Magento\Framework\Serialize\Serializer\Json as Serializer; -use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\App\ObjectManager; use Magento\Eav\Model\Cache\Type as CacheType; use Magento\Eav\Model\Entity\Attribute; @@ -37,9 +37,9 @@ abstract class AbstractFrontend implements \Magento\Eav\Model\Entity\Attribute\F private $cache; /** - * @var StoreResolverInterface + * @var StoreManagerInterface */ - private $storeResolver; + private $storeManager; /** * @var Serializer @@ -66,22 +66,25 @@ abstract class AbstractFrontend implements \Magento\Eav\Model\Entity\Attribute\F /** * @param BooleanFactory $attrBooleanFactory * @param CacheInterface $cache - * @param StoreResolverInterface $storeResolver + * @param $storeResolver @deprecated * @param array $cacheTags + * @param StoreManagerInterface $storeManager * @param Serializer $serializer * @codeCoverageIgnore + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( BooleanFactory $attrBooleanFactory, CacheInterface $cache = null, - StoreResolverInterface $storeResolver = null, + $storeResolver = null, array $cacheTags = null, + StoreManagerInterface $storeManager = null, Serializer $serializer = null ) { $this->_attrBooleanFactory = $attrBooleanFactory; $this->cache = $cache ?: ObjectManager::getInstance()->get(CacheInterface::class); - $this->storeResolver = $storeResolver ?: ObjectManager::getInstance()->get(StoreResolverInterface::class); $this->cacheTags = $cacheTags ?: self::$defaultCacheTags; + $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Serializer::class); } @@ -299,7 +302,7 @@ public function getSelectOptions() { $cacheKey = 'attribute-navigation-option-' . $this->getAttribute()->getAttributeCode() . '-' . - $this->storeResolver->getCurrentStoreId(); + $this->storeManager->getStore()->getId(); $optionString = $this->cache->load($cacheKey); if (false === $optionString) { $options = $this->getAttribute()->getSource()->getAllOptions(); diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php index 61146d7e3860..ced56f313dd7 100644 --- a/app/code/Magento/Eav/Setup/EavSetup.php +++ b/app/code/Magento/Eav/Setup/EavSetup.php @@ -942,6 +942,7 @@ public function updateAttribute($entityTypeId, $id, $field, $value = null, $sort * @param mixed $value * @param int $sortOrder * @return $this + * @throws LocalizedException */ private function _updateAttribute($entityTypeId, $id, $field, $value = null, $sortOrder = null) { @@ -972,11 +973,15 @@ private function _updateAttribute($entityTypeId, $id, $field, $value = null, $so return $this; } } + $attributeId = $this->getAttributeId($entityTypeId, $id); + if (false === $attributeId) { + throw new LocalizedException(__('Attribute with ID: "%1" does not exist', $id)); + } $this->setup->updateTableRow( 'eav_attribute', 'attribute_id', - $this->getAttributeId($entityTypeId, $id), + $attributeId, $field, $value, 'entity_type_id', @@ -994,6 +999,7 @@ private function _updateAttribute($entityTypeId, $id, $field, $value = null, $so * @param string|array $field * @param mixed $value * @return $this + * @throws LocalizedException */ private function _updateAttributeAdditionalData($entityTypeId, $id, $field, $value = null) { @@ -1022,6 +1028,11 @@ private function _updateAttributeAdditionalData($entityTypeId, $id, $field, $val return $this; } } + + $attributeId = $this->getAttributeId($entityTypeId, $id); + if (false === $attributeId) { + throw new LocalizedException(__('Attribute with ID: "%1" does not exist', $id)); + } $this->setup->updateTableRow( $this->setup->getTable($additionalTable), 'attribute_id', diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php index 5dd00fb3bc4f..7ff2dc0b9f0d 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php @@ -8,7 +8,8 @@ use Magento\Eav\Model\Entity\Attribute\Frontend\DefaultFrontend; use Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory; use Magento\Framework\Serialize\Serializer\Json as Serializer; -use Magento\Store\Api\StoreResolverInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Api\Data\StoreInterface; use Magento\Framework\App\CacheInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; @@ -31,9 +32,14 @@ class DefaultFrontendTest extends \PHPUnit_Framework_TestCase private $serializerMock; /** - * @var StoreResolverInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $storeResolverMock; + private $storeManagerMock; + + /** + * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; /** * @var CacheInterface|\PHPUnit_Framework_MockObject_MockObject @@ -64,7 +70,9 @@ protected function setUp() ->getMock(); $this->serializerMock = $this->getMockBuilder(Serializer::class) ->getMock(); - $this->storeResolverMock = $this->getMockBuilder(StoreResolverInterface::class) + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + ->getMockForAbstractClass(); + $this->storeMock = $this->getMockBuilder(StoreInterface::class) ->getMockForAbstractClass(); $this->cacheMock = $this->getMockBuilder(CacheInterface::class) ->getMockForAbstractClass(); @@ -83,7 +91,7 @@ protected function setUp() [ '_attrBooleanFactory' => $this->booleanFactory, 'cache' => $this->cacheMock, - 'storeResolver' => $this->storeResolverMock, + 'storeManager' => $this->storeManagerMock, 'serializer' => $this->serializerMock, '_attribute' => $this->attributeMock, 'cacheTags' => $this->cacheTags @@ -188,8 +196,11 @@ public function testGetSelectOptions() $options = ['option1', 'option2']; $serializedOptions = "{['option1', 'option2']}"; - $this->storeResolverMock->expects($this->once()) - ->method('getCurrentStoreId') + $this->storeManagerMock->expects($this->once()) + ->method('getStore') + ->willReturn($this->storeMock); + $this->storeMock->expects($this->once()) + ->method('getId') ->willReturn($storeId); $this->attributeMock->expects($this->once()) ->method('getAttributeCode') diff --git a/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml index 403abb6fdcad..b63f79233383 100644 --- a/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml @@ -22,7 +22,7 @@ - jpg jpeg gif png svg + jpg jpeg gif png 2097152 theme/design_config_fileUploader/save diff --git a/app/code/Magento/GiftMessage/i18n/en_US.csv b/app/code/Magento/GiftMessage/i18n/en_US.csv index 5c82c8e5aeb8..bac6989bd01a 100644 --- a/app/code/Magento/GiftMessage/i18n/en_US.csv +++ b/app/code/Magento/GiftMessage/i18n/en_US.csv @@ -22,7 +22,7 @@ OK,OK "Gift Options","Gift Options" "Gift Message","Gift Message" "Do you have any gift items in your order?","Do you have any gift items in your order?" -"Add gift options","Add gift options" +"Add Gift Options","Add Gift Options" "Gift Options for the Entire Order","Gift Options for the Entire Order" "Leave this box blank if you don\'t want to leave a gift message for the entire order.","Leave this box blank if you don\'t want to leave a gift message for the entire order." "Gift Options for Individual Items","Gift Options for Individual Items" @@ -30,14 +30,14 @@ OK,OK "Leave a box blank if you don\'t want to add a gift message for that item.","Leave a box blank if you don\'t want to add a gift message for that item." "Add Gift Options for the Entire Order","Add Gift Options for the Entire Order" "You can leave this box blank if you don\'t want to add a gift message for this address.","You can leave this box blank if you don\'t want to add a gift message for this address." -"Add gift options for Individual Items","Add gift options for Individual Items" +"Add Gift Options for Individual Items","Add Gift Options for Individual Items" "You can leave this box blank if you don\'t want to add a gift message for the item.","You can leave this box blank if you don\'t want to add a gift message for the item." "Gift Message (optional)","Gift Message (optional)" To:,To: From:,From: Message:,Message: Update,Update -"Gift options","Gift options" +"Gift Options","Gift Options" Edit,Edit Delete,Delete "Allow Gift Messages on Order Level","Allow Gift Messages on Order Level" diff --git a/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml b/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml index 8fbb6918d711..6155cfc37c4a 100644 --- a/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml +++ b/app/code/Magento/GiftMessage/view/frontend/templates/inline.phtml @@ -14,7 +14,7 @@
getItemsHasMesssages() || $block->getEntityHasMessage()): ?> checked="checked" class="checkbox" /> - +
@@ -148,7 +148,7 @@
getItemsHasMesssages() || $block->getEntityHasMessage()): ?> checked="checked" class="checkbox" /> - +
@@ -197,7 +197,7 @@
getItemsHasMesssages()): ?> checked="checked" class="checkbox" /> - +
diff --git a/app/code/Magento/Integration/etc/adminhtml/system.xml b/app/code/Magento/Integration/etc/adminhtml/system.xml index 73ebb70c2a22..97aec083e7ab 100644 --- a/app/code/Magento/Integration/etc/adminhtml/system.xml +++ b/app/code/Magento/Integration/etc/adminhtml/system.xml @@ -48,6 +48,18 @@ Timeout for OAuth consumer credentials Post request within X seconds. + + + + + Maximum Number of authentication failures to lock out account. + + + + Period of time in seconds after which account will be unlocked. + + + diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Source/CarrierRequestDataHydrator.php b/app/code/Magento/Inventory/Controller/Adminhtml/Source/CarrierRequestDataHydrator.php new file mode 100644 index 000000000000..edb0795b86e3 --- /dev/null +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Source/CarrierRequestDataHydrator.php @@ -0,0 +1,114 @@ +carrierLinkFactory = $carrierLinkFactory; + $this->dataObjectHelper = $dataObjectHelper; + $this->shippingConfig = $shippingConfig; + } + + /** + * @param SourceInterface $source + * @param array $requestData + * @return SourceInterface + * @throws InputException + */ + public function hydrate(SourceInterface $source, array $requestData) + { + $useDefaultCarrierConfig = isset($requestData[SourceInterface::USE_DEFAULT_CARRIER_CONFIG]) + && true === (bool)$requestData[SourceInterface::USE_DEFAULT_CARRIER_CONFIG]; + + $carrierLinks = []; + if (false === $useDefaultCarrierConfig + && isset($requestData['carrier_codes']) + && is_array($requestData['carrier_codes']) + ) { + $this->checkCarrierCodes($requestData['carrier_codes']); + $carrierLinks = $this->createCarrierLinks($requestData['carrier_codes']); + } + + $source->setUseDefaultCarrierConfig($useDefaultCarrierConfig); + $source->setCarrierLinks($carrierLinks); + return $source; + } + + /** + * @param array $carrierCodes + * @return void + * @throws InputException + */ + private function checkCarrierCodes(array $carrierCodes) + { + $availableCarriers = $this->shippingConfig->getAllCarriers(); + + if (count(array_intersect_key(array_flip($carrierCodes), $availableCarriers)) !== count($carrierCodes)) { + throw new InputException(__('Wrong carrier codes data')); + } + } + + /** + * @param array $carrierCodes + * @return SourceCarrierLinkInterface[] + */ + private function createCarrierLinks(array $carrierCodes) + { + $carrierLinks = []; + foreach ($carrierCodes as $carrierCode) { + /** @var SourceCarrierLinkInterface $carrierLink */ + $carrierLink = $this->carrierLinkFactory->create(); + $this->dataObjectHelper->populateWithArray( + $carrierLink, + [ + SourceCarrierLinkInterface::CARRIER_CODE => $carrierCode, + ], + SourceCarrierLinkInterface::class + ); + $carrierLinks[] = $carrierLink; + } + return $carrierLinks; + } +} diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Source/Edit.php b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Edit.php new file mode 100644 index 000000000000..d3711442fa24 --- /dev/null +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Edit.php @@ -0,0 +1,70 @@ +sourceRepository = $sourceRepository; + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $sourceId = $this->getRequest()->getParam(SourceInterface::SOURCE_ID); + try { + $source = $this->sourceRepository->get($sourceId); + + /** @var Page $result */ + $result = $this->resultFactory->create(ResultFactory::TYPE_PAGE); + $result->setActiveMenu('Magento_Inventory::source') + ->addBreadcrumb(__('Edit Source'), __('Edit Source')); + $result->getConfig() + ->getTitle() + ->prepend(__('Edit Source: %1', $source->getName())); + } catch (NoSuchEntityException $e) { + /** @var Redirect $result */ + $result = $this->resultRedirectFactory->create(); + $this->messageManager->addErrorMessage( + __('Source with id "%1" does not exist.', $sourceId) + ); + $result->setPath('*/*'); + } + return $result; + } +} diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Source/Index.php b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Index.php new file mode 100644 index 000000000000..94c37beef49a --- /dev/null +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Index.php @@ -0,0 +1,34 @@ +resultFactory->create(ResultFactory::TYPE_PAGE); + $resultPage->setActiveMenu('Magento_Inventory::source') + ->addBreadcrumb(__('Sources'), __('List')); + $resultPage->getConfig()->getTitle()->prepend(__('Manage Sources')); + return $resultPage; + } +} diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Source/InlineEdit.php b/app/code/Magento/Inventory/Controller/Adminhtml/Source/InlineEdit.php new file mode 100644 index 000000000000..46a2d67fd08a --- /dev/null +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Source/InlineEdit.php @@ -0,0 +1,93 @@ +hydrator = $hydrator; + $this->sourceRepository = $sourceRepository; + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $errorMessages = []; + $request = $this->getRequest(); + $requestData = $request->getParam('items', []); + + if ($request->isXmlHttpRequest() && $request->isPost() && $requestData) { + foreach ($requestData as $itemData) { + try { + $source = $this->sourceRepository->get( + $itemData[SourceInterface::SOURCE_ID] + ); + $source = $this->hydrator->hydrate($source, $itemData); + $this->sourceRepository->save($source); + } catch (NoSuchEntityException $e) { + $errorMessages[] = __( + '[ID: %1] The Source does not exist.', + $itemData[SourceInterface::SOURCE_ID] + ); + } catch (CouldNotSaveException $e) { + $errorMessages[] = + __('[ID: %1] ', $itemData[SourceInterface::SOURCE_ID]) + . $e->getMessage(); + } + } + } else { + $errorMessages[] = __('Please correct the data sent.'); + } + + /** @var Json $resultJson */ + $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $resultJson->setData([ + 'messages' => $errorMessages, + 'error' => count($errorMessages), + ]); + return $resultJson; + } +} diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Source/NewAction.php b/app/code/Magento/Inventory/Controller/Adminhtml/Source/NewAction.php new file mode 100644 index 000000000000..a9b96061a8ae --- /dev/null +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Source/NewAction.php @@ -0,0 +1,34 @@ +resultFactory->create(ResultFactory::TYPE_PAGE); + $resultPage->setActiveMenu('Magento_Inventory::source'); + $resultPage->getConfig()->getTitle()->prepend(__('New Source')); + return $resultPage; + } +} diff --git a/app/code/Magento/Inventory/Controller/Adminhtml/Source/Save.php b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Save.php new file mode 100644 index 000000000000..7d1185a6c911 --- /dev/null +++ b/app/code/Magento/Inventory/Controller/Adminhtml/Source/Save.php @@ -0,0 +1,181 @@ +sourceFactory = $sourceFactory; + $this->sourceRepository = $sourceRepository; + $this->dataObjectHelper = $dataObjectHelper; + $this->registry = $registry; + $this->carrierRequestDataHydrator = $carrierRequestDataHydrator; + } + + /** + * {@inheritdoc} + */ + public function execute() + { + $resultRedirect = $this->resultRedirectFactory->create(); + $requestData = $this->getRequest()->getParam('general'); + if ($this->getRequest()->isPost() && $requestData) { + try { + $sourceId = !empty($requestData[SourceInterface::SOURCE_ID]) + ? $requestData[SourceInterface::SOURCE_ID] : null; + + $sourceId = $this->processSave($sourceId, $requestData); + // Keep data for plugins on Save controller. Now we can not call separate services from one form. + $this->registry->register(self::REGISTRY_SOURCE_ID_KEY, $sourceId); + + $this->messageManager->addSuccessMessage(__('The Source has been saved.')); + $this->processRedirectAfterSuccessSave($resultRedirect, $sourceId); + + } catch (NoSuchEntityException $e) { + $this->messageManager->addErrorMessage(__('The Source does not exist.')); + $this->processRedirectAfterFailureSave($resultRedirect); + } catch (CouldNotSaveException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $this->processRedirectAfterFailureSave($resultRedirect, $sourceId); + } catch (InputException $e) { + $this->messageManager->addErrorMessage($e->getMessage()); + $this->processRedirectAfterFailureSave($resultRedirect, $sourceId); + } catch (Exception $e) { + $this->messageManager->addErrorMessage(__('Could not save source')); + $this->processRedirectAfterFailureSave($resultRedirect, $sourceId); + } + } else { + $this->messageManager->addErrorMessage(__('Wrong request.')); + $this->processRedirectAfterFailureSave($resultRedirect); + } + return $resultRedirect; + } + + /** + * @param int $sourceId + * @param array $requestData + * @return int + */ + private function processSave($sourceId, array $requestData) + { + if ($sourceId) { + $source = $this->sourceRepository->get($sourceId); + } else { + /** @var SourceInterface $source */ + $source = $this->sourceFactory->create(); + } + $source = $this->dataObjectHelper->populateWithArray($source, $requestData, SourceInterface::class); + $source = $this->carrierRequestDataHydrator->hydrate($source, $requestData); + + $sourceId = $this->sourceRepository->save($source); + return $sourceId; + } + + /** + * @param Redirect $resultRedirect + * @param int $sourceId + * @return void + */ + private function processRedirectAfterSuccessSave(Redirect $resultRedirect, $sourceId) + { + if ($this->getRequest()->getParam('back')) { + $resultRedirect->setPath('*/*/edit', [ + SourceInterface::SOURCE_ID => $sourceId, + '_current' => true, + ]); + } elseif ($this->getRequest()->getParam('redirect_to_new')) { + $resultRedirect->setPath('*/*/new', [ + '_current' => true, + ]); + } else { + $resultRedirect->setPath('*/*/'); + } + } + + /** + * @param Redirect $resultRedirect + * @param int|null $sourceId + * @return void + */ + private function processRedirectAfterFailureSave(Redirect $resultRedirect, $sourceId = null) + { + if (null === $sourceId) { + $resultRedirect->setPath('*/*/'); + } else { + $resultRedirect->setPath('*/*/edit', [ + SourceInterface::SOURCE_ID => $sourceId, + '_current' => true, + ]); + } + } +} diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/LICENSE.txt b/app/code/Magento/Inventory/LICENSE.txt similarity index 100% rename from app/code/Magento/CatalogInventoryConfigurableProduct/LICENSE.txt rename to app/code/Magento/Inventory/LICENSE.txt diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/LICENSE_AFL.txt b/app/code/Magento/Inventory/LICENSE_AFL.txt similarity index 100% rename from app/code/Magento/CatalogInventoryConfigurableProduct/LICENSE_AFL.txt rename to app/code/Magento/Inventory/LICENSE_AFL.txt diff --git a/app/code/Magento/Inventory/Model/OptionSource/CarrierSource.php b/app/code/Magento/Inventory/Model/OptionSource/CarrierSource.php new file mode 100644 index 000000000000..1bb60603feed --- /dev/null +++ b/app/code/Magento/Inventory/Model/OptionSource/CarrierSource.php @@ -0,0 +1,56 @@ +shippingConfig = $shippingConfig; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + if (null === $this->sourceData) { + $carriers = $this->shippingConfig->getAllCarriers(); + foreach ($carriers as $carrier) { + $this->sourceData[] = [ + 'value' => $carrier->getCarrierCode(), + 'label' => $carrier->getConfigData('title'), + ]; + } + } + return $this->sourceData; + } +} diff --git a/app/code/Magento/Inventory/Model/OptionSource/RegionSource.php b/app/code/Magento/Inventory/Model/OptionSource/RegionSource.php new file mode 100644 index 000000000000..f352bac25079 --- /dev/null +++ b/app/code/Magento/Inventory/Model/OptionSource/RegionSource.php @@ -0,0 +1,51 @@ +regionCollectionFactory = $regionCollectionFactory; + } + + /** + * {@inheritdoc} + */ + public function toOptionArray() + { + if (null === $this->sourceData) { + $regionCollection = $this->regionCollectionFactory->create(); + $this->sourceData = $regionCollection->toOptionArray(); + } + return $this->sourceData; + } +} diff --git a/app/code/Magento/Inventory/Model/ResourceModel/Source.php b/app/code/Magento/Inventory/Model/ResourceModel/Source.php new file mode 100644 index 000000000000..08e24a89fa22 --- /dev/null +++ b/app/code/Magento/Inventory/Model/ResourceModel/Source.php @@ -0,0 +1,76 @@ +sourceCarrierLinkManagement = $sourceCarrierLinkManagement; + } + + /** + * @inheritdoc + */ + protected function _construct() + { + $this->_init(InstallSchema::TABLE_NAME_SOURCE, SourceInterface::SOURCE_ID); + } + + /** + * @inheritdoc + */ + public function load(AbstractModel $object, $value, $field = null) + { + parent::load($object, $value, $field); + /** @var SourceInterface $object */ + $this->sourceCarrierLinkManagement->loadCarrierLinksBySource($object); + return $this; + } + + /** + * @inheritdoc + */ + public function save(AbstractModel $object) + { + $connection = $this->getConnection(); + $connection->beginTransaction(); + try { + parent::save($object); + /** @var SourceInterface $object */ + $this->sourceCarrierLinkManagement->saveCarrierLinksBySource($object); + $connection->commit(); + } catch (Exception $e) { + $connection->rollBack(); + throw $e; + } + return $this; + } +} diff --git a/app/code/Magento/Inventory/Model/ResourceModel/Source/Collection.php b/app/code/Magento/Inventory/Model/ResourceModel/Source/Collection.php new file mode 100644 index 000000000000..533b6fe7647c --- /dev/null +++ b/app/code/Magento/Inventory/Model/ResourceModel/Source/Collection.php @@ -0,0 +1,78 @@ +sourceCarrierLinkManagement = $sourceCarrierLinkManagement; + } + + /** + * @inheritdoc + */ + protected function _construct() + { + $this->_init(SourceModel::class, ResourceSource::class); + } + + /** + * @inheritdoc + */ + public function load($printQuery = false, $logQuery = false) + { + parent::load($printQuery, $logQuery); + + foreach ($this->_items as $item) { + /** @var SourceInterface $item */ + $this->sourceCarrierLinkManagement->loadCarrierLinksBySource($item); + } + return $this; + } +} diff --git a/app/code/Magento/Inventory/Model/ResourceModel/SourceCarrierLink.php b/app/code/Magento/Inventory/Model/ResourceModel/SourceCarrierLink.php new file mode 100644 index 000000000000..bb2d9f225bbe --- /dev/null +++ b/app/code/Magento/Inventory/Model/ResourceModel/SourceCarrierLink.php @@ -0,0 +1,25 @@ +_init(InstallSchema::TABLE_NAME_SOURCE_CARRIER_LINK, 'source_carrier_link_id'); + } +} diff --git a/app/code/Magento/Inventory/Model/ResourceModel/SourceCarrierLink/Collection.php b/app/code/Magento/Inventory/Model/ResourceModel/SourceCarrierLink/Collection.php new file mode 100644 index 000000000000..cb5ecd67a171 --- /dev/null +++ b/app/code/Magento/Inventory/Model/ResourceModel/SourceCarrierLink/Collection.php @@ -0,0 +1,33 @@ +_init(SourceCarrierLinkModel::class, ResourceSourceCarrierLink::class); + } + + /** + * Id field name getter + * + * @return string + */ + public function getIdFieldName() + { + return 'source_carrier_link_id'; + } +} diff --git a/app/code/Magento/Inventory/Model/Source.php b/app/code/Magento/Inventory/Model/Source.php new file mode 100644 index 000000000000..8d37778f00af --- /dev/null +++ b/app/code/Magento/Inventory/Model/Source.php @@ -0,0 +1,351 @@ +_init(\Magento\Inventory\Model\ResourceModel\Source::class); + } + + /** + * @inheritdoc + */ + public function getSourceId() + { + return $this->getData(SourceInterface::SOURCE_ID); + } + + /** + * @inheritdoc + */ + public function setSourceId($sourceId) + { + $this->setData(SourceInterface::SOURCE_ID, $sourceId); + } + + /** + * @inheritdoc + */ + public function getName() + { + return $this->getData(SourceInterface::NAME); + } + + /** + * @inheritdoc + */ + public function setName($name) + { + $this->setData(SourceInterface::NAME, $name); + } + + /** + * @inheritdoc + */ + public function getEmail() + { + return $this->getData(SourceInterface::EMAIL); + } + + /** + * @inheritdoc + */ + public function setEmail($email) + { + $this->setData(SourceInterface::EMAIL, $email); + } + + /** + * @inheritdoc + */ + public function getContactName() + { + return $this->getData(SourceInterface::CONTACT_NAME); + } + + /** + * @inheritdoc + */ + public function setContactName($contactName) + { + $this->setData(SourceInterface::CONTACT_NAME, $contactName); + } + + /** + * @inheritdoc + */ + public function isEnabled() + { + return $this->getData(SourceInterface::ENABLED); + } + + /** + * @inheritdoc + */ + public function setEnabled($enabled) + { + $this->setData(SourceInterface::ENABLED, $enabled); + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return $this->getData(SourceInterface::DESCRIPTION); + } + + /** + * @inheritdoc + */ + public function setDescription($description) + { + $this->setData(SourceInterface::DESCRIPTION, $description); + } + + /** + * @inheritdoc + */ + public function getLatitude() + { + return $this->getData(SourceInterface::LATITUDE); + } + + /** + * @inheritdoc + */ + public function setLatitude($latitude) + { + $this->setData(SourceInterface::LATITUDE, $latitude); + } + + /** + * @inheritdoc + */ + public function getLongitude() + { + return $this->getData(SourceInterface::LONGITUDE); + } + + /** + * @inheritdoc + */ + public function setLongitude($longitude) + { + $this->setData(SourceInterface::LONGITUDE, $longitude); + } + + /** + * @inheritdoc + */ + public function getCountryId() + { + return $this->getData(SourceInterface::COUNTRY_ID); + } + + /** + * @inheritdoc + */ + public function setCountryId($countryId) + { + $this->setData(SourceInterface::COUNTRY_ID, $countryId); + } + + /** + * @inheritdoc + */ + public function getRegionId() + { + return $this->getData(SourceInterface::REGION_ID); + } + + /** + * @inheritdoc + */ + public function setRegionId($regionId) + { + $this->setData(SourceInterface::REGION_ID, $regionId); + } + + /** + * @inheritdoc + */ + public function getRegion() + { + return $this->getData(SourceInterface::REGION); + } + + /** + * @inheritdoc + */ + public function setRegion($region) + { + $this->setData(SourceInterface::REGION, $region); + } + + /** + * @inheritdoc + */ + public function getCity() + { + return $this->getData(SourceInterface::CITY); + } + + /** + * @inheritdoc + */ + public function setCity($city) + { + $this->setData(SourceInterface::CITY, $city); + } + + /** + * @inheritdoc + */ + public function getStreet() + { + return $this->getData(SourceInterface::STREET); + } + + /** + * @inheritdoc + */ + public function setStreet($street) + { + $this->setData(SourceInterface::STREET, $street); + } + + /** + * @inheritdoc + */ + public function getPostcode() + { + return $this->getData(SourceInterface::POSTCODE); + } + + /** + * @inheritdoc + */ + public function setPostcode($postcode) + { + $this->setData(SourceInterface::POSTCODE, $postcode); + } + + /** + * @inheritdoc + */ + public function getPhone() + { + return $this->getData(SourceInterface::PHONE); + } + + /** + * @inheritdoc + */ + public function setPhone($phone) + { + $this->setData(SourceInterface::PHONE, $phone); + } + + /** + * @inheritdoc + */ + public function getFax() + { + return $this->getData(SourceInterface::FAX); + } + + /** + * @inheritdoc + */ + public function setFax($fax) + { + $this->setData(SourceInterface::FAX, $fax); + } + + /** + * @inheritdoc + */ + public function getPriority() + { + return $this->getData(SourceInterface::PRIORITY); + } + + /** + * @inheritdoc + */ + public function setPriority($priority) + { + $this->setData(SourceInterface::PRIORITY, $priority); + } + + /** + * {@inheritdoc} + */ + public function isUseDefaultCarrierConfig() + { + return $this->getData(self::USE_DEFAULT_CARRIER_CONFIG); + } + + /** + * {@inheritdoc} + */ + public function setUseDefaultCarrierConfig($useDefaultCarrierConfig) + { + return $this->setData(self::USE_DEFAULT_CARRIER_CONFIG, $useDefaultCarrierConfig); + } + + /** + * @inheritdoc + */ + public function getCarrierLinks() + { + return $this->getData(SourceInterface::CARRIER_LINKS); + } + + /** + * @inheritdoc + */ + public function setCarrierLinks($carrierLinks) + { + $this->setData(SourceInterface::CARRIER_LINKS, $carrierLinks); + } + + /** + * @inheritdoc + */ + public function getExtensionAttributes() + { + $extensionAttributes = $this->_getExtensionAttributes(); + if (null === $extensionAttributes) { + $extensionAttributes = $this->extensionAttributesFactory->create(SourceInterface::class); + $this->setExtensionAttributes($extensionAttributes); + } + return $extensionAttributes; + } + + /** + * @inheritdoc + */ + public function setExtensionAttributes( + \Magento\InventoryApi\Api\Data\SourceExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Inventory/Model/SourceCarrierLink.php b/app/code/Magento/Inventory/Model/SourceCarrierLink.php new file mode 100644 index 000000000000..a1f3043a5357 --- /dev/null +++ b/app/code/Magento/Inventory/Model/SourceCarrierLink.php @@ -0,0 +1,79 @@ +_init(\Magento\Inventory\Model\ResourceModel\SourceCarrierLink::class); + } + + /** + * @inheritDoc + */ + public function getCarrierCode() + { + return $this->getData(SourceCarrierLinkInterface::CARRIER_CODE); + } + + /** + * @inheritDoc + */ + public function setCarrierCode($carrierCode) + { + $this->setData(SourceCarrierLinkInterface::CARRIER_CODE, $carrierCode); + } + + /** + * @inheritDoc + */ + public function getPosition() + { + return $this->getData(SourceCarrierLinkInterface::POSITION); + } + + /** + * @inheritDoc + */ + public function setPosition($position) + { + $this->setData(SourceCarrierLinkInterface::POSITION, $position); + } + + /** + * @inheritDoc + */ + public function getExtensionAttributes() + { + $extensionAttributes = $this->_getExtensionAttributes(); + if (null === $extensionAttributes) { + $extensionAttributes = $this->extensionAttributesFactory->create(SourceCarrierLinkInterface::class); + $this->setExtensionAttributes($extensionAttributes); + } + return $extensionAttributes; + } + + /** + * @inheritDoc + */ + public function setExtensionAttributes( + \Magento\InventoryApi\Api\Data\SourceCarrierLinkExtensionInterface $extensionAttributes + ) { + return $this->_setExtensionAttributes($extensionAttributes); + } +} diff --git a/app/code/Magento/Inventory/Model/SourceCarrierLinkManagement.php b/app/code/Magento/Inventory/Model/SourceCarrierLinkManagement.php new file mode 100644 index 000000000000..c71ced6523d6 --- /dev/null +++ b/app/code/Magento/Inventory/Model/SourceCarrierLinkManagement.php @@ -0,0 +1,139 @@ +connection = $connection; + $this->resourceSourceCarrierLink = $resourceSourceCarrierLink; + $this->collectionProcessor = $collectionProcessor; + $this->carrierLinkCollectionFactory = $carrierLinkCollectionFactory; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + } + + /** + * @inheritdoc + */ + public function saveCarrierLinksBySource(SourceInterface $source) + { + if (is_array($source->getCarrierLinks())) { + try { + $this->deleteCurrentCarrierLinks($source); + if (!empty($source->getCarrierLinks())) { + $this->saveNewCarrierLinks($source); + } + } catch (\Exception $e) { + throw new StateException(__('Could not update Carrier Links'), $e); + } + } + } + + /** + * @param SourceInterface $source + * @return void + */ + private function deleteCurrentCarrierLinks(SourceInterface $source) + { + $connection = $this->connection->getConnection(); + $connection->delete( + $connection->getTableName(InstallSchema::TABLE_NAME_SOURCE_CARRIER_LINK), + $connection->quoteInto('source_id = ?', $source->getSourceId()) + ); + } + + /** + * @param SourceInterface $source + * @return void + */ + private function saveNewCarrierLinks(SourceInterface $source) + { + $carrierLinkData = []; + foreach ($source->getCarrierLinks() as $carrierLink) { + $carrierLinkData[] = [ + 'source_id' => $source->getSourceId(), + SourceCarrierLinkInterface::CARRIER_CODE => $carrierLink->getCarrierCode(), + SourceCarrierLinkInterface::POSITION => $carrierLink->getPosition(), + ]; + } + + $connection = $this->connection->getConnection(); + $connection->insertMultiple( + $connection->getTableName(InstallSchema::TABLE_NAME_SOURCE_CARRIER_LINK), + $carrierLinkData + ); + } + + /** + * @inheritdoc + */ + public function loadCarrierLinksBySource(SourceInterface $source) + { + $searchCriteria = $this->searchCriteriaBuilder + ->addFilter(SourceInterface::SOURCE_ID, $source->getSourceId()) + ->create(); + + /** @var ResourceSourceCarrierLink\Collection $collection */ + $collection = $this->carrierLinkCollectionFactory->create(); + $this->collectionProcessor->process($searchCriteria, $collection); + + $source->setCarrierLinks($collection->getItems()); + } +} diff --git a/app/code/Magento/Inventory/Model/SourceCarrierLinkManagementInterface.php b/app/code/Magento/Inventory/Model/SourceCarrierLinkManagementInterface.php new file mode 100644 index 000000000000..3f5ae25d3d68 --- /dev/null +++ b/app/code/Magento/Inventory/Model/SourceCarrierLinkManagementInterface.php @@ -0,0 +1,36 @@ +resourceSource = $resourceSource; + $this->sourceFactory = $sourceFactory; + $this->collectionProcessor = $collectionProcessor; + $this->sourceCollectionFactory = $sourceCollectionFactory; + $this->sourceSearchResultsFactory = $sourceSearchResultsFactory; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->logger = $logger; + } + + /** + * @inheritdoc + */ + public function save(SourceInterface $source) + { + try { + $this->resourceSource->save($source); + return $source->getSourceId(); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + throw new CouldNotSaveException(__('Could not save source'), $e); + } + } + + /** + * @inheritdoc + */ + public function get($sourceId) + { + $source = $this->sourceFactory->create(); + $this->resourceSource->load($source, $sourceId, SourceInterface::SOURCE_ID); + + if (!$source->getSourceId()) { + throw NoSuchEntityException::singleField(SourceInterface::SOURCE_ID, $sourceId); + } + return $source; + } + + /** + * @inheritdoc + */ + public function getList(SearchCriteriaInterface $searchCriteria = null) + { + $collection = $this->sourceCollectionFactory->create(); + + if (null === $searchCriteria) { + $searchCriteria = $this->searchCriteriaBuilder->create(); + } else { + $this->collectionProcessor->process($searchCriteria, $collection); + } + + $searchResult = $this->sourceSearchResultsFactory->create(); + $searchResult->setItems($collection->getItems()); + $searchResult->setTotalCount($collection->getSize()); + $searchResult->setSearchCriteria($searchCriteria); + return $searchResult; + } +} diff --git a/app/code/Magento/Inventory/README.md b/app/code/Magento/Inventory/README.md new file mode 100644 index 000000000000..2defd616b65f --- /dev/null +++ b/app/code/Magento/Inventory/README.md @@ -0,0 +1,5 @@ +# Inventory + +**Inventory** provides implementation for inventory management. +See [concept documentation](https://github.com/magento-engcom/magento2/wiki/Technical-Vision.-Catalog-Inventory) +for further information. diff --git a/app/code/Magento/Inventory/Setup/InstallSchema.php b/app/code/Magento/Inventory/Setup/InstallSchema.php new file mode 100644 index 000000000000..d9fbffe8b477 --- /dev/null +++ b/app/code/Magento/Inventory/Setup/InstallSchema.php @@ -0,0 +1,335 @@ +startSetup(); + + $sourceTable = $this->createSourceTable($setup); + $sourceTable = $this->addAddressFields($sourceTable); + $sourceTable = $this->addContactInfoFields($sourceTable); + $sourceTable = $this->addSourceCarrierFields($sourceTable); + $setup->getConnection()->createTable($sourceTable); + + $setup->getConnection()->createTable($this->createSourceCarrierLinkTable($setup)); + $setup->endSetup(); + } + + /** + * @param SchemaSetupInterface $setup + * @return Table + */ + private function createSourceTable(SchemaSetupInterface $setup) + { + $sourceTable = $setup->getTable(InstallSchema::TABLE_NAME_SOURCE); + + return $setup->getConnection()->newTable( + $sourceTable + )->setComment( + 'Inventory Source Table' + )->addColumn( + SourceInterface::SOURCE_ID, + Table::TYPE_INTEGER, + null, + [ + InstallSchema::OPTION_IDENTITY => true, + InstallSchema::OPTION_UNSIGNED => true, + InstallSchema::OPTION_NULLABLE => false, + InstallSchema::OPTION_PRIMARY => true, + ], + 'Source ID' + )->addColumn( + SourceInterface::NAME, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => false, + ], + 'Source Name' + )->addColumn( + SourceInterface::ENABLED, + Table::TYPE_SMALLINT, + null, + [ + InstallSchema::OPTION_NULLABLE => false, + InstallSchema::OPTION_UNSIGNED => true, + InstallSchema::OPTION_DEFAULT => 1, + ], + 'Defines Is Source Enabled' + )->addColumn( + SourceInterface::DESCRIPTION, + Table::TYPE_TEXT, + 1000, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'Description' + )->addColumn( + SourceInterface::LATITUDE, + Table::TYPE_DECIMAL, + null, + [ + InstallSchema::OPTION_PRECISION => InstallSchema::LATLON_PRECISION_LAT, + InstallSchema::OPTION_SCALE => InstallSchema::LATLON_SCALE, + InstallSchema::OPTION_UNSIGNED => false, + InstallSchema::OPTION_NULLABLE => true, + ], + 'Latitude' + )->addColumn( + SourceInterface::LONGITUDE, + Table::TYPE_DECIMAL, + null, + [ + InstallSchema::OPTION_PRECISION => InstallSchema::LATLON_PRECISION_LON, + InstallSchema::OPTION_SCALE => InstallSchema::LATLON_SCALE, + InstallSchema::OPTION_UNSIGNED => false, + InstallSchema::OPTION_NULLABLE => true, + ], + 'Longitude' + )->addColumn( + SourceInterface::PRIORITY, + Table::TYPE_SMALLINT, + null, + [ + InstallSchema::OPTION_NULLABLE => true, + InstallSchema::OPTION_UNSIGNED => true, + ], + 'Priority' + ); + } + + /** + * @param Table $sourceTable + * @return Table + */ + private function addAddressFields(Table $sourceTable) + { + $sourceTable->addColumn( + SourceInterface::COUNTRY_ID, + Table::TYPE_TEXT, + 2, + [ + InstallSchema::OPTION_NULLABLE => false, + ], + 'Country Id' + )->addColumn( + SourceInterface::REGION_ID, + Table::TYPE_INTEGER, + null, + [ + InstallSchema::OPTION_NULLABLE => true, + InstallSchema::OPTION_UNSIGNED => true, + ], + 'Region Id' + )->addColumn( + SourceInterface::REGION, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'Region' + )->addColumn( + SourceInterface::CITY, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'City' + )->addColumn( + SourceInterface::STREET, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'Street' + )->addColumn( + SourceInterface::POSTCODE, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => false, + ], + 'Postcode' + ); + return $sourceTable; + } + + /** + * @param Table $sourceTable + * @return Table + */ + private function addContactInfoFields(Table $sourceTable) + { + $sourceTable->addColumn( + SourceInterface::CONTACT_NAME, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'Contact Name' + )->addColumn( + SourceInterface::EMAIL, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'Email' + )->addColumn( + SourceInterface::PHONE, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'Phone' + )->addColumn( + SourceInterface::FAX, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => true, + ], + 'Fax' + ); + return $sourceTable; + } + + /** + * @param Table $sourceTable + * @return Table + */ + private function addSourceCarrierFields(Table $sourceTable) + { + $sourceTable->addColumn( + 'use_default_carrier_config', + Table::TYPE_SMALLINT, + null, + [ + 'unsigned' => true, + 'nullable' => false, + 'default' => '1' + ], + 'Use default carrier configuration' + ); + return $sourceTable; + } + + /** + * @param SchemaSetupInterface $setup + * @return Table + */ + private function createSourceCarrierLinkTable(SchemaSetupInterface $setup) + { + $sourceCarrierLinkTable = $setup->getTable(InstallSchema::TABLE_NAME_SOURCE_CARRIER_LINK); + $sourceTable = $setup->getTable(InstallSchema::TABLE_NAME_SOURCE); + + return $setup->getConnection()->newTable( + $sourceCarrierLinkTable + )->setComment( + 'Inventory Source Carrier Link Table' + )->addColumn( + 'source_carrier_link_id', + Table::TYPE_INTEGER, + null, + [ + InstallSchema::OPTION_IDENTITY => true, + InstallSchema::OPTION_UNSIGNED => true, + InstallSchema::OPTION_NULLABLE => false, + InstallSchema::OPTION_PRIMARY => true, + ], + 'Source Carrier Link ID' + )->addColumn( + SourceInterface::SOURCE_ID, + Table::TYPE_INTEGER, + null, + [ + InstallSchema::OPTION_NULLABLE => false, + InstallSchema::OPTION_UNSIGNED => true, + ], + 'Source ID' + )->addColumn( + SourceCarrierLinkInterface::CARRIER_CODE, + Table::TYPE_TEXT, + 255, + [ + InstallSchema::OPTION_NULLABLE => false, + ], + 'Carrier Code' + )->addColumn( + 'position', + Table::TYPE_SMALLINT, + null, + [ + InstallSchema::OPTION_NULLABLE => true, + InstallSchema::OPTION_UNSIGNED => true, + ], + 'Position' + )->addForeignKey( + $setup->getFkName( + $sourceCarrierLinkTable, + SourceInterface::SOURCE_ID, + $sourceTable, + SourceInterface::SOURCE_ID + ), + SourceInterface::SOURCE_ID, + $sourceTable, + SourceInterface::SOURCE_ID, + AdapterInterface::FK_ACTION_CASCADE + ); + } +} diff --git a/app/code/Magento/Inventory/Test/Unit/Model/SourceRepositoryTest.php b/app/code/Magento/Inventory/Test/Unit/Model/SourceRepositoryTest.php new file mode 100644 index 000000000000..291b7ca2e607 --- /dev/null +++ b/app/code/Magento/Inventory/Test/Unit/Model/SourceRepositoryTest.php @@ -0,0 +1,309 @@ +resourceSource = $this->getMockBuilder(SourceResource::class)->disableOriginalConstructor()->getMock(); + $this->searchCriteriaBuilder = $this->getMockBuilder(SearchCriteriaBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + $this->sourceFactory = $this->getMockBuilder(SourceInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->collectionProcessor = $this->getMockBuilder(CollectionProcessorInterface::class) + ->disableOriginalConstructor() + ->setMethods(['process']) + ->getMock(); + $this->sourceCollectionFactory = $this->getMockBuilder(SourceCollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->sourceSearchResultsFactory = $this->getMockBuilder(SourceSearchResultsInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->source = $this->getMockBuilder(Source::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->model = $objectManager->getObject( + \Magento\Inventory\Model\SourceRepository::class, + [ + 'resourceSource' => $this->resourceSource, + 'sourceFactory' => $this->sourceFactory, + 'collectionProcessor' => $this->collectionProcessor, + 'sourceCollectionFactory' => $this->sourceCollectionFactory, + 'sourceSearchResultsFactory' => $this->sourceSearchResultsFactory, + 'searchCriteriaBuilder' => $this->searchCriteriaBuilder, + 'logger' => $this->loggerMock, + ] + ); + } + + public function testSave() + { + $sourceId = 42; + + $this->source + ->expects($this->once()) + ->method('getSourceId') + ->willReturn($sourceId); + $this->resourceSource + ->expects($this->once()) + ->method('save') + ->with($this->source); + + self::assertEquals($sourceId, $this->model->save($this->source)); + } + + /** + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + */ + public function testSaveErrorExpectsException() + { + $message = 'some message'; + + $this->resourceSource + ->expects($this->once()) + ->method('save') + ->willThrowException(new \Exception($message)); + + $this->loggerMock + ->expects($this->once()) + ->method('error') + ->with($message); + + $this->model->save($this->source); + } + + public function testGet() + { + $sourceId = 345; + + $this->source + ->expects($this->once()) + ->method('getSourceId') + ->willReturn($sourceId); + $this->sourceFactory + ->expects($this->once()) + ->method('create') + ->willReturn($this->source); + $this->resourceSource + ->expects($this->once()) + ->method('load') + ->with($this->source, $sourceId, SourceInterface::SOURCE_ID); + + self::assertSame($this->source, $this->model->get($sourceId)); + } + + /** + * @expectedException \Magento\Framework\Exception\NoSuchEntityException + */ + public function testGetErrorExpectsException() + { + $sourceId = 345; + + $this->source + ->expects($this->once()) + ->method('getSourceId') + ->willReturn(null); + $this->sourceFactory + ->expects($this->once()) + ->method('create') + ->willReturn($this->source); + $this->resourceSource->expects($this->once()) + ->method('load') + ->with( + $this->source, + $sourceId, + SourceInterface::SOURCE_ID + ); + + $this->model->get($sourceId); + } + + public function testGetListWithSearchCriteria() + { + $items = [ + $this->getMockBuilder(Source::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(Source::class)->disableOriginalConstructor()->getMock() + ]; + $totalCount = 2; + $searchCriteria = $this->getMockBuilder(SearchCriteriaInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $sourceCollection = $this->getMockBuilder(SourceCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $sourceCollection + ->expects($this->once()) + ->method('getItems') + ->willReturn($items); + $sourceCollection + ->expects($this->once()) + ->method('getSize') + ->willReturn($totalCount); + $this->sourceCollectionFactory + ->expects($this->once()) + ->method('create') + ->willReturn($sourceCollection); + + $searchResults = $this->getMockBuilder(SourceSearchResultsInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $searchResults + ->expects($this->once()) + ->method('setItems') + ->with($items); + $searchResults + ->expects($this->once()) + ->method('setTotalCount') + ->with($totalCount); + $searchResults + ->expects($this->once()) + ->method('setSearchCriteria') + ->with($searchCriteria); + $this->sourceSearchResultsFactory + ->expects($this->once()) + ->method('create') + ->willReturn($searchResults); + + $this->collectionProcessor + ->expects($this->once()) + ->method('process') + ->with($searchCriteria, $sourceCollection); + + self::assertSame($searchResults, $this->model->getList($searchCriteria)); + } + + public function testGetListWithoutSearchCriteria() + { + $items = [ + $this->getMockBuilder(Source::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(Source::class)->disableOriginalConstructor()->getMock() + ]; + $totalCount = 2; + + $searchCriteria = $this->getMockBuilder(SearchCriteriaInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->searchCriteriaBuilder + ->expects($this->once()) + ->method('create') + ->willReturn($searchCriteria); + + $sourceCollection = $this->getMockBuilder(SourceCollection::class) + ->disableOriginalConstructor() + ->getMock(); + $sourceCollection + ->expects($this->once()) + ->method('getItems') + ->willReturn($items); + $sourceCollection + ->expects($this->once()) + ->method('getSize') + ->willReturn($totalCount); + $this->sourceCollectionFactory + ->expects($this->once()) + ->method('create') + ->willReturn($sourceCollection); + + $searchResults = $this->getMockBuilder(SourceSearchResultsInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $searchResults + ->expects($this->once()) + ->method('setItems') + ->with($items); + $searchResults + ->expects($this->once()) + ->method('setTotalCount') + ->with($totalCount); + $searchResults + ->expects($this->once()) + ->method('setSearchCriteria') + ->with($searchCriteria); + $this->sourceSearchResultsFactory + ->expects($this->once()) + ->method('create') + ->willReturn($searchResults); + + $this->collectionProcessor + ->expects($this->never()) + ->method('process'); + + self::assertSame($searchResults, $this->model->getList()); + } +} diff --git a/app/code/Magento/Inventory/Ui/DataProvider/SourceDataProvider.php b/app/code/Magento/Inventory/Ui/DataProvider/SourceDataProvider.php new file mode 100644 index 000000000000..997e72545e8a --- /dev/null +++ b/app/code/Magento/Inventory/Ui/DataProvider/SourceDataProvider.php @@ -0,0 +1,130 @@ +sourceRepository = $sourceRepository; + $this->searchResultFactory = $searchResultFactory; + } + + /** + * {@inheritdoc} + */ + public function getData() + { + $data = parent::getData(); + if ('inventory_source_form_data_source' === $this->name) { + // It is need for support of several fieldsets. + // For details see \Magento\Ui\Component\Form::getDataSourceData + if ($data['totalRecords'] > 0) { + $sourceId = $data['items'][0][SourceInterface::SOURCE_ID]; + $sourceGeneralData = $data['items'][0]; + $sourceGeneralData['carrier_codes'] = $this->getAssignedCarrierCodes($sourceId); + $dataForSingle[$sourceId] = [ + 'general' => $sourceGeneralData, + ]; + $data = $dataForSingle; + } else { + $data = []; + } + } + return $data; + } + + /** + * {@inheritdoc} + */ + public function getSearchResult() + { + $searchCriteria = $this->getSearchCriteria(); + $result = $this->sourceRepository->getList($searchCriteria); + + $searchResult = $this->searchResultFactory->create( + $result->getItems(), + $result->getTotalCount(), + $searchCriteria, + SourceInterface::SOURCE_ID + ); + return $searchResult; + } + + /** + * @param int $sourceId + * @return array + */ + private function getAssignedCarrierCodes($sourceId) + { + $source = $this->sourceRepository->get($sourceId); + + $carrierCodes = []; + foreach ($source->getCarrierLinks() as $carrierLink) { + $carrierCodes[] = $carrierLink->getCarrierCode(); + } + return $carrierCodes; + } +} diff --git a/app/code/Magento/Inventory/composer.json b/app/code/Magento/Inventory/composer.json new file mode 100644 index 000000000000..45581db5f40b --- /dev/null +++ b/app/code/Magento/Inventory/composer.json @@ -0,0 +1,27 @@ +{ + "name": "magento/module-inventory", + "description": "N/A", + "require": { + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", + "magento/framework": "100.2.*", + "magento/module-inventory-api": "100.0.*", + "magento/module-backend": "100.2.*", + "magento/module-directory": "100.2.*", + "magento/module-shipping": "100.2.*", + "magento/module-ui": "100.2.*" + }, + "type": "magento2-module", + "version": "100.0.0-dev", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\Inventory\\": "" + } + } +} diff --git a/app/code/Magento/Inventory/etc/adminhtml/di.xml b/app/code/Magento/Inventory/etc/adminhtml/di.xml new file mode 100644 index 000000000000..f1989475949e --- /dev/null +++ b/app/code/Magento/Inventory/etc/adminhtml/di.xml @@ -0,0 +1,15 @@ + + + + + + + inventory_source_form.inventory_source_form + + + diff --git a/app/code/Magento/Inventory/etc/adminhtml/menu.xml b/app/code/Magento/Inventory/etc/adminhtml/menu.xml new file mode 100644 index 000000000000..bd4c7e9e1e3f --- /dev/null +++ b/app/code/Magento/Inventory/etc/adminhtml/menu.xml @@ -0,0 +1,19 @@ + + + + + + + diff --git a/app/code/Magento/Inventory/etc/adminhtml/routes.xml b/app/code/Magento/Inventory/etc/adminhtml/routes.xml new file mode 100644 index 000000000000..72e6c53b9857 --- /dev/null +++ b/app/code/Magento/Inventory/etc/adminhtml/routes.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/Inventory/etc/di.xml b/app/code/Magento/Inventory/etc/di.xml new file mode 100644 index 000000000000..3c6091ff5561 --- /dev/null +++ b/app/code/Magento/Inventory/etc/di.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/Inventory/etc/module.xml b/app/code/Magento/Inventory/etc/module.xml new file mode 100644 index 000000000000..2ef165277a57 --- /dev/null +++ b/app/code/Magento/Inventory/etc/module.xml @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/registration.php b/app/code/Magento/Inventory/registration.php similarity index 82% rename from app/code/Magento/CatalogInventoryConfigurableProduct/registration.php rename to app/code/Magento/Inventory/registration.php index d784dbbbe606..55b1734a5250 100644 --- a/app/code/Magento/CatalogInventoryConfigurableProduct/registration.php +++ b/app/code/Magento/Inventory/registration.php @@ -6,6 +6,6 @@ \Magento\Framework\Component\ComponentRegistrar::register( \Magento\Framework\Component\ComponentRegistrar::MODULE, - 'Magento_CatalogInventoryConfigurableProduct', + 'Magento_Inventory', __DIR__ ); diff --git a/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_edit.xml b/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_edit.xml new file mode 100644 index 000000000000..8944441fd693 --- /dev/null +++ b/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_edit.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_index.xml b/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_index.xml new file mode 100644 index 000000000000..a1a413733288 --- /dev/null +++ b/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_index.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_new.xml b/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_new.xml new file mode 100644 index 000000000000..68d4c1c00309 --- /dev/null +++ b/app/code/Magento/Inventory/view/adminhtml/layout/inventory_source_new.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/code/Magento/Inventory/view/adminhtml/ui_component/inventory_source_form.xml b/app/code/Magento/Inventory/view/adminhtml/ui_component/inventory_source_form.xml new file mode 100644 index 000000000000..e7726ee34109 --- /dev/null +++ b/app/code/Magento/Inventory/view/adminhtml/ui_component/inventory_source_form.xml @@ -0,0 +1,268 @@ + + +
+ + + inventory_source_form.inventory_source_form_data_source + + Source Information + templates/form/collapsible + + + data + inventory_source_form + + inventory_source_form.inventory_source_form_data_source + + + + + + + inventory_source_listing_columns + + inventory_source_listing.inventory_source_listing_data_source + + + + + + source_id + + + + + + id + source_id + + + + + + true + + + + + + + + + + + + + + + + + + + + + + false + + source_id + true + inventory_source_listing.inventory_source_listing.inventory_source_listing_columns.ids + + + + inventory_source_listing.inventory_source_listing.inventory_source_listing_columns_editor + startEdit + + ${ $.$data.rowIndex } + true + + + + + + + source_id + + + + + textRange + + asc + + + + + text + + + text + + true + + + + + + + text + + text + + + false + + + + + text + + text + + true + + + + false + + + + + + select + select + + + + + + + + + text + + text + + true + + + + false + + + + + text + + text + + true + + + + false + + + + + false + select + select + + + + + + false + select + select + + + + + + text + + false + + + + + text + + text + + + false + + + + + text + + text + + + false + + + + + text + + text + + true + + + + false + + + + + text + + text + + true + + + + false + + + + + text + + text + + true + + + + false + + + + + text + + text + + true + + + + + + + + + inventory/source/edit + + + + source_id + + + + diff --git a/app/code/Magento/InventoryApi/Api/Data/SourceCarrierLinkInterface.php b/app/code/Magento/InventoryApi/Api/Data/SourceCarrierLinkInterface.php new file mode 100644 index 000000000000..9d0fb91a8d9d --- /dev/null +++ b/app/code/Magento/InventoryApi/Api/Data/SourceCarrierLinkInterface.php @@ -0,0 +1,69 @@ +" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/InventoryApi/LICENSE_AFL.txt b/app/code/Magento/InventoryApi/LICENSE_AFL.txt new file mode 100644 index 000000000000..f39d641b18a1 --- /dev/null +++ b/app/code/Magento/InventoryApi/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/InventoryApi/README.md b/app/code/Magento/InventoryApi/README.md new file mode 100644 index 000000000000..e10a610def87 --- /dev/null +++ b/app/code/Magento/InventoryApi/README.md @@ -0,0 +1,5 @@ +# InventoryApi + +**InventoryApi** provides interfaces for inventory management. +See [concept documentation](https://github.com/magento-engcom/magento2/wiki/Technical-Vision.-Catalog-Inventory) +for further information. diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/composer.json b/app/code/Magento/InventoryApi/composer.json similarity index 53% rename from app/code/Magento/CatalogInventoryConfigurableProduct/composer.json rename to app/code/Magento/InventoryApi/composer.json index 2bfd4a377188..36fb986952cb 100644 --- a/app/code/Magento/CatalogInventoryConfigurableProduct/composer.json +++ b/app/code/Magento/InventoryApi/composer.json @@ -1,16 +1,12 @@ { - "name": "magento/module-catalog-inventory-configurable-product", + "name": "magento/module-inventory-api", "description": "N/A", "require": { "php": "7.0.2|7.0.4|~7.0.6|~7.1.0", - "magento/module-catalog-inventory": "100.2.*", "magento/framework": "100.2.*" }, - "suggest": { - "magento/module-configurable-product": "100.2.*" - }, "type": "magento2-module", - "version": "100.2.0-dev", + "version": "100.0.0-dev", "license": [ "OSL-3.0", "AFL-3.0" @@ -20,7 +16,7 @@ "registration.php" ], "psr-4": { - "Magento\\CatalogInventoryConfigurableProduct\\": "" + "Magento\\InventoryApi\\": "" } } } diff --git a/app/code/Magento/InventoryApi/etc/acl.xml b/app/code/Magento/InventoryApi/etc/acl.xml new file mode 100644 index 000000000000..b353c2a55244 --- /dev/null +++ b/app/code/Magento/InventoryApi/etc/acl.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/app/code/Magento/CatalogInventoryConfigurableProduct/etc/module.xml b/app/code/Magento/InventoryApi/etc/module.xml similarity index 76% rename from app/code/Magento/CatalogInventoryConfigurableProduct/etc/module.xml rename to app/code/Magento/InventoryApi/etc/module.xml index 9c0772cf2ae5..9b4ae307339b 100644 --- a/app/code/Magento/CatalogInventoryConfigurableProduct/etc/module.xml +++ b/app/code/Magento/InventoryApi/etc/module.xml @@ -6,5 +6,5 @@ */ --> - + diff --git a/app/code/Magento/InventoryApi/etc/webapi.xml b/app/code/Magento/InventoryApi/etc/webapi.xml new file mode 100644 index 000000000000..6c413af4829c --- /dev/null +++ b/app/code/Magento/InventoryApi/etc/webapi.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/InventoryApi/registration.php b/app/code/Magento/InventoryApi/registration.php new file mode 100644 index 000000000000..ce0a9c85a875 --- /dev/null +++ b/app/code/Magento/InventoryApi/registration.php @@ -0,0 +1,11 @@ +addressRenderer->format($address, 'html'); } + + /** + * @inheritdoc + */ + public function getChildHtml($alias = '', $useCache = true) + { + $layout = $this->getLayout(); + + if ($alias || !$layout) { + return parent::getChildHtml($alias, $useCache); + } + + $childNames = $layout->getChildNames($this->getNameInLayout()); + $outputChildNames = array_diff($childNames, ['extra_customer_info']); + + $out = ''; + foreach ($outputChildNames as $childName) { + $out .= $layout->renderElement($childName, $useCache); + } + + return $out; + } } diff --git a/app/code/Magento/Sales/Block/Order/View.php b/app/code/Magento/Sales/Block/Order/View.php index f63de0a39435..e77711d28b99 100644 --- a/app/code/Magento/Sales/Block/Order/View.php +++ b/app/code/Magento/Sales/Block/Order/View.php @@ -27,9 +27,9 @@ class View extends \Magento\Framework\View\Element\Template protected $_coreRegistry = null; /** - * @var \Magento\Customer\Model\Session + * @var \Magento\Framework\App\Http\Context */ - protected $_customerSession; + protected $httpContext; /** * @var \Magento\Payment\Helper\Data diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Creditmemo.php index e276eccb85b5..823385c4b78d 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo.php @@ -439,14 +439,14 @@ public function canVoid() */ public static function getStates() { - if (is_null(self::$_states)) { - self::$_states = [ + if (is_null(static::$_states)) { + static::$_states = [ self::STATE_OPEN => __('Pending'), self::STATE_REFUNDED => __('Refunded'), self::STATE_CANCELED => __('Canceled'), ]; } - return self::$_states; + return static::$_states; } /** @@ -461,11 +461,11 @@ public function getStateName($stateId = null) $stateId = $this->getState(); } - if (is_null(self::$_states)) { - self::getStates(); + if (is_null(static::$_states)) { + static::getStates(); } - if (isset(self::$_states[$stateId])) { - return self::$_states[$stateId]; + if (isset(static::$_states[$stateId])) { + return static::$_states[$stateId]; } return __('Unknown State'); } diff --git a/app/code/Magento/Sales/Model/Order/Invoice.php b/app/code/Magento/Sales/Model/Order/Invoice.php index 8b9fb9151325..622bc7783c11 100644 --- a/app/code/Magento/Sales/Model/Order/Invoice.php +++ b/app/code/Magento/Sales/Model/Order/Invoice.php @@ -543,14 +543,14 @@ public function addItem(\Magento\Sales\Model\Order\Invoice\Item $item) */ public static function getStates() { - if (null === self::$_states) { - self::$_states = [ + if (null === static::$_states) { + static::$_states = [ self::STATE_OPEN => __('Pending'), self::STATE_PAID => __('Paid'), self::STATE_CANCELED => __('Canceled'), ]; } - return self::$_states; + return static::$_states; } /** @@ -565,11 +565,11 @@ public function getStateName($stateId = null) $stateId = $this->getState(); } - if (null === self::$_states) { - self::getStates(); + if (null === static::$_states) { + static::getStates(); } - if (isset(self::$_states[$stateId])) { - return self::$_states[$stateId]; + if (isset(static::$_states[$stateId])) { + return static::$_states[$stateId]; } return __('Unknown State'); } diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Total/DefaultTotal.php b/app/code/Magento/Sales/Model/Order/Pdf/Total/DefaultTotal.php index 3a31aca1b5df..715f68d3210a 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Total/DefaultTotal.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Total/DefaultTotal.php @@ -165,6 +165,6 @@ public function getAmount() */ public function getTitleDescription() { - return $this->getSource()->getDataUsingMethod($this->getTitleSourceField()); + return $this->getSource()->getOrder()->getData($this->getTitleSourceField()); } } diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml index d63c6f23f401..38f5e7bdfc4a 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml @@ -56,7 +56,7 @@ - + diff --git a/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml b/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml index 2d404cb96f97..8065b7e23613 100644 --- a/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml +++ b/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml @@ -13,7 +13,9 @@ - + + + diff --git a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml index a2bc20e1a281..b953c6e9ea0d 100644 --- a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml +++ b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml @@ -104,6 +104,7 @@
+ getChildHtml('form_additional_info'); ?>
diff --git a/app/code/Magento/Shipping/Model/Config.php b/app/code/Magento/Shipping/Model/Config.php index 5f477387b154..bf42766be6b7 100644 --- a/app/code/Magento/Shipping/Model/Config.php +++ b/app/code/Magento/Shipping/Model/Config.php @@ -8,6 +8,8 @@ namespace Magento\Shipping\Model; +use Magento\Shipping\Model\Carrier\AbstractCarrierInterface; + /** * Class Config * @api @@ -58,7 +60,7 @@ public function __construct( * Retrieve active system carriers * * @param mixed $store - * @return array + * @return AbstractCarrierInterface[] */ public function getActiveCarriers($store = null) { @@ -79,7 +81,7 @@ public function getActiveCarriers($store = null) * Retrieve all system carriers * * @param mixed $store - * @return array + * @return AbstractCarrierInterface[] */ public function getAllCarriers($store = null) { diff --git a/app/code/Magento/Shipping/Model/Shipping/Labels.php b/app/code/Magento/Shipping/Model/Shipping/Labels.php index 77ec66b74fa2..b1709b0a1699 100644 --- a/app/code/Magento/Shipping/Model/Shipping/Labels.php +++ b/app/code/Magento/Shipping/Model/Shipping/Labels.php @@ -122,7 +122,6 @@ public function requestToShipment(Shipment $orderShipment) || !$storeInfo->getName() || !$storeInfo->getPhone() || !$originStreet1 - || !$shipperRegionCode || !$this->_scopeConfig->getValue( Shipment::XML_PATH_STORE_CITY, ScopeInterface::SCOPE_STORE, diff --git a/app/code/Magento/Shipping/Test/Unit/Model/Shipping/LabelsTest.php b/app/code/Magento/Shipping/Test/Unit/Model/Shipping/LabelsTest.php index c6272fa6c9d3..8269da7c7fa0 100644 --- a/app/code/Magento/Shipping/Test/Unit/Model/Shipping/LabelsTest.php +++ b/app/code/Magento/Shipping/Test/Unit/Model/Shipping/LabelsTest.php @@ -5,8 +5,7 @@ */ namespace Magento\Shipping\Test\Unit\Model\Shipping; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Framework\DataObject; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Sales\Model\Order\Shipment; use Magento\Store\Model\ScopeInterface; @@ -39,6 +38,16 @@ class LabelsTest extends \PHPUnit_Framework_TestCase */ protected $region; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $carrierFactory; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $user; + protected function setUp() { $this->request = $this->getMockBuilder(\Magento\Shipping\Model\Shipment\Request::class) @@ -49,32 +58,32 @@ protected function setUp() ->setMethods(['create']) ->getMock(); $requestFactory->expects(static::any())->method('create')->willReturn($this->request); - - $carrier = $this->getMockBuilder(\Magento\Shipping\Model\Carrier\AbstractCarrier::class) + $this->carrierFactory = $this->getMockBuilder(\Magento\Shipping\Model\CarrierFactory::class) ->disableOriginalConstructor() + ->setMethods(['create']) ->getMock(); - - $carrierFactory = $this->getMockBuilder(\Magento\Shipping\Model\CarrierFactory::class) + $storeManager = $this->getStoreManager(); + $this->user = $this->getMockBuilder(\Magento\User\Model\User::class) ->disableOriginalConstructor() - ->setMethods(['create']) + ->setMethods(['getFirstname', 'getLastname', 'getEmail', 'getName']) ->getMock(); - $carrierFactory->expects(static::any())->method('create')->willReturn($carrier); - $storeManager = $this->getStoreManager(); - $authSession = $this->getAuthSession(); + $authSession = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) + ->disableOriginalConstructor() + ->setMethods(['getUser']) + ->getMock(); + $authSession->expects(static::any())->method('getUser')->willReturn($this->user); $regionFactory = $this->getRegionFactory(); - $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config::class) ->disableOriginalConstructor() ->setMethods(['getValue']) ->getMock(); - - $objectManagerHelper = new ObjectManager($this); + $objectManagerHelper = new ObjectManagerHelper($this); $this->labels = $objectManagerHelper->getObject( \Magento\Shipping\Model\Shipping\Labels::class, [ 'shipmentRequestFactory' => $requestFactory, - 'carrierFactory' => $carrierFactory, + 'carrierFactory' => $this->carrierFactory, 'storeManager' => $storeManager, 'scopeConfig' => $this->scopeConfig, 'authSession' => $authSession, @@ -84,14 +93,21 @@ protected function setUp() } /** - * @covers \Magento\Shipping\Model\Shipping\Labels + * @dataProvider requestToShipmentDataProvider */ - public function testRequestToShipment() + public function testRequestToShipment($regionId) { + $carrier = $this->getMockBuilder(\Magento\Shipping\Model\Carrier\AbstractCarrier::class) + ->disableOriginalConstructor() + ->getMock(); + $this->carrierFactory->expects(static::any())->method('create')->willReturn($carrier); $order = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() ->getMock(); - + $this->user->expects($this->atLeastOnce())->method('getFirstname')->willReturn('John'); + $this->user->expects($this->atLeastOnce())->method('getLastname')->willReturn('Doe'); + $this->user->expects($this->once())->method('getName')->willReturn('John Doe'); + $this->user->expects($this->once())->method('getEmail')->willReturn('admin@admin.test.com'); $shippingMethod = $this->getMockBuilder(\Magento\Framework\DataObject::class) ->disableOriginalConstructor() ->setMethods(['getCarrierCode']) @@ -125,7 +141,7 @@ public function testRequestToShipment() $this->scopeConfig->expects(static::any()) ->method('getValue') ->willReturnMap([ - [Shipment::XML_PATH_STORE_REGION_ID, ScopeInterface::SCOPE_STORE, $storeId, 'CA'], + [Shipment::XML_PATH_STORE_REGION_ID, ScopeInterface::SCOPE_STORE, $storeId, $regionId], [Shipment::XML_PATH_STORE_ADDRESS1, ScopeInterface::SCOPE_STORE, $storeId, 'Beverly Heals'], ['general/store_information', ScopeInterface::SCOPE_STORE, $storeId, [ 'name' => 'General Store', 'phone' => '(244)1500301' @@ -135,38 +151,35 @@ public function testRequestToShipment() [Shipment::XML_PATH_STORE_COUNTRY_ID, ScopeInterface::SCOPE_STORE, $storeId, 'US'], [Shipment::XML_PATH_STORE_ADDRESS2, ScopeInterface::SCOPE_STORE, $storeId, '1st Park Avenue'], ]); - $this->labels->requestToShipment($shipment); } /** - * @return \PHPUnit_Framework_MockObject_MockObject + * @expectedException \Magento\Framework\Exception\LocalizedException + * @dataProvider requestToShipmentLocalizedExceptionDataProvider */ - protected function getAuthSession() + public function testRequestToShipmentLocalizedException($isShipmentCarrierNotNull) { - $user = $this->getMockBuilder(\Magento\User\Model\User::class) + $order = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() - ->setMethods(['getFirstname', 'getLastname', 'getEmail', 'getName']) ->getMock(); - $user->expects(static::exactly(2)) - ->method('getFirstname') - ->willReturn('John'); - $user->expects(static::exactly(2)) - ->method('getLastname') - ->willReturn('Doe'); - $user->expects(static::once()) - ->method('getName') - ->willReturn('John Doe'); - $user->expects(static::once()) - ->method('getEmail') - ->willReturn('admin@admin.test.com'); - - $authSession = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) + $shipment = $this->getMockBuilder(\Magento\Sales\Model\Order\Shipment::class) ->disableOriginalConstructor() - ->setMethods(['getUser']) ->getMock(); - $authSession->expects(static::any())->method('getUser')->willReturn($user); - return $authSession; + $shippingMethod = $this->getMockBuilder(\Magento\Framework\DataObject::class) + ->disableOriginalConstructor() + ->setMethods(['getCarrierCode']) + ->getMock(); + $order->expects($this->atLeastOnce()) + ->method('getShippingMethod') + ->with(true) + ->willReturn($shippingMethod); + $this->carrierFactory + ->expects(static::any()) + ->method('create') + ->willReturn($isShipmentCarrierNotNull ? $shippingMethod : null); + $shipment->expects($this->once())->method('getOrder')->willReturn($order); + $this->labels->requestToShipment($shipment); } /** @@ -246,4 +259,36 @@ protected function getRecipientAddress() ->willReturn(1); return $address; } + + /** + * Data provider to testRequestToShipment + * @return array + */ + public function requestToShipmentDataProvider() + { + return [ + [ + 'CA' + ], + [ + null + ] + ]; + } + + /** + * Data provider to testRequestToShipmentLocalizedException + * @return array + */ + public function requestToShipmentLocalizedExceptionDataProvider() + { + return [ + [ + true + ], + [ + false + ] + ]; + } } diff --git a/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php b/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php index ad7809ecee2e..4c931a156a5f 100644 --- a/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php +++ b/app/code/Magento/Store/App/Config/Source/RuntimeConfigSource.php @@ -7,9 +7,12 @@ use Magento\Framework\App\Config\ConfigSourceInterface; use Magento\Framework\App\DeploymentConfig; +use Magento\Store\Model\Group; use Magento\Store\Model\ResourceModel\Website\CollectionFactory as WebsiteCollectionFactory; use Magento\Store\Model\ResourceModel\Group\CollectionFactory as GroupCollectionFactory; use Magento\Store\Model\ResourceModel\Store\CollectionFactory as StoreCollectionFactory; +use Magento\Store\Model\Store; +use Magento\Store\Model\Website; use Magento\Store\Model\WebsiteFactory; use Magento\Store\Model\GroupFactory; use Magento\Store\Model\StoreFactory; @@ -100,13 +103,13 @@ public function get($path = '') if ($this->canUseDatabase()) { switch ($scopePool) { case 'websites': - $data = $this->getWebsitesData($scopeCode); + $data['websites'] = $this->getWebsitesData($scopeCode); break; case 'groups': - $data = $this->getGroupsData($scopeCode); + $data['groups'] = $this->getGroupsData($scopeCode); break; case 'stores': - $data = $this->getStoresData($scopeCode); + $data['stores'] = $this->getStoresData($scopeCode); break; default: $data = [ @@ -127,14 +130,15 @@ public function get($path = '') */ private function getWebsitesData($code = null) { - if ($code) { + if ($code !== null) { $website = $this->websiteFactory->create(); $website->load($code); - $data = $website->getData(); + $data[$code] = $website->getData(); } else { $collection = $this->websiteCollectionFactory->create(); $collection->setLoadDefault(true); $data = []; + /** @var Website $website */ foreach ($collection as $website) { $data[$website->getCode()] = $website->getData(); } @@ -148,14 +152,15 @@ private function getWebsitesData($code = null) */ private function getGroupsData($id = null) { - if ($id) { + if ($id !== null) { $group = $this->groupFactory->create(); $group->load($id); - $data = $group->getData(); + $data[$id] = $group->getData(); } else { $collection = $this->groupCollectionFactory->create(); $collection->setLoadDefault(true); $data = []; + /** @var Group $group */ foreach ($collection as $group) { $data[$group->getId()] = $group->getData(); } @@ -169,14 +174,21 @@ private function getGroupsData($id = null) */ private function getStoresData($code = null) { - if ($code) { + if ($code !== null) { $store = $this->storeFactory->create(); - $store->load($code, 'code'); - $data = $store->getData(); + + if (is_numeric($code)) { + $store->load($code); + } else { + $store->load($code, 'code'); + } + + $data[$code] = $store->getData(); } else { $collection = $this->storeCollectionFactory->create(); $collection->setLoadDefault(true); $data = []; + /** @var Store $store */ foreach ($collection as $store) { $data[$store->getCode()] = $store->getData(); } diff --git a/app/code/Magento/Store/App/Config/Type/Scopes.php b/app/code/Magento/Store/App/Config/Type/Scopes.php index d9dc0278e862..b1f266c8b0cf 100644 --- a/app/code/Magento/Store/App/Config/Type/Scopes.php +++ b/app/code/Magento/Store/App/Config/Type/Scopes.php @@ -34,6 +34,7 @@ public function __construct( ConfigSourceInterface $source ) { $this->source = $source; + $this->data = new DataObject(); } /** @@ -41,8 +42,10 @@ public function __construct( */ public function get($path = '') { - if (!$this->data) { - $this->data = new DataObject($this->source->get()); + $patchChunks = explode("/", $path); + + if (!$this->data->getData($path) || count($patchChunks) == 1) { + $this->data->addData($this->source->get($path)); } return $this->data->getData($path); @@ -55,6 +58,6 @@ public function get($path = '') */ public function clean() { - $this->data = null; + $this->data = new DataObject(); } } diff --git a/app/code/Magento/Store/Model/Config/Processor/Fallback.php b/app/code/Magento/Store/Model/Config/Processor/Fallback.php index 2578ea79a41d..91926cd23f9c 100644 --- a/app/code/Magento/Store/Model/Config/Processor/Fallback.php +++ b/app/code/Magento/Store/Model/Config/Processor/Fallback.php @@ -6,7 +6,16 @@ namespace Magento\Store\Model\Config\Processor; use Magento\Framework\App\Config\Spi\PostProcessorInterface; +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ResourceConnection; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\WebsiteInterface; use Magento\Store\App\Config\Type\Scopes; +use Magento\Store\Model\ResourceModel\Store; +use Magento\Store\Model\ResourceModel\Store\AllStoresCollectionFactory; +use Magento\Store\Model\ResourceModel\Website; +use Magento\Store\Model\ResourceModel\Website\AllWebsitesCollection; +use Magento\Store\Model\ResourceModel\Website\AllWebsitesCollectionFactory; /** * Fallback through different scopes and merge them @@ -18,13 +27,57 @@ class Fallback implements PostProcessorInterface */ private $scopes; + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var array + */ + private $storeData = []; + + /** + * @var array + */ + private $websiteData = []; + + /** + * @var Store + */ + private $storeResource; + + /** + * @var Website + */ + private $websiteResource; + + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + /** * Fallback constructor. + * * @param Scopes $scopes + * @param ResourceConnection $resourceConnection + * @param Store $storeResource + * @param Website $websiteResource + * @param DeploymentConfig $deploymentConfig */ - public function __construct(Scopes $scopes) - { + public function __construct( + Scopes $scopes, + ResourceConnection $resourceConnection, + Store $storeResource, + Website $websiteResource, + DeploymentConfig $deploymentConfig + ) { $this->scopes = $scopes; + $this->resourceConnection = $resourceConnection; + $this->storeResource = $storeResource; + $this->websiteResource = $websiteResource; + $this->deploymentConfig = $deploymentConfig; } /** @@ -32,6 +85,14 @@ public function __construct(Scopes $scopes) */ public function process(array $data) { + if ($this->deploymentConfig->isDbAvailable()) {//read only from db + $this->storeData = $this->storeResource->readAllStores(); + $this->websiteData = $this->websiteResource->readAllWebsites(); + } else { + $this->storeData = $this->scopes->get('stores'); + $this->websiteData = $this->scopes->get('websites'); + } + $defaultConfig = isset($data['default']) ? $data['default'] : []; $result = [ 'default' => $defaultConfig, @@ -55,12 +116,14 @@ public function process(array $data) * @param array $websitesConfig * @return array */ - private function prepareWebsitesConfig(array $defaultConfig, array $websitesConfig) - { + private function prepareWebsitesConfig( + array $defaultConfig, + array $websitesConfig + ) { $result = []; - foreach ((array)$this->scopes->get('websites') as $websiteData) { - $code = $websiteData['code']; - $id = $websiteData['website_id']; + foreach ((array)$this->websiteData as $website) { + $code = $website['code']; + $id = $website['website_id']; $websiteConfig = isset($websitesConfig[$code]) ? $websitesConfig[$code] : []; $result[$code] = array_replace_recursive($defaultConfig, $websiteConfig); $result[$id] = $result[$code]; @@ -76,15 +139,19 @@ private function prepareWebsitesConfig(array $defaultConfig, array $websitesConf * @param array $storesConfig * @return array */ - private function prepareStoresConfig(array $defaultConfig, array $websitesConfig, array $storesConfig) - { + private function prepareStoresConfig( + array $defaultConfig, + array $websitesConfig, + array $storesConfig + ) { $result = []; - foreach ((array)$this->scopes->get('stores') as $storeData) { - $code = $storeData['code']; - $id = $storeData['store_id']; + + foreach ((array)$this->storeData as $store) { + $code = $store['code']; + $id = $store['store_id']; $websiteConfig = []; - if (isset($storeData['website_id'])) { - $websiteConfig = $this->getWebsiteConfig($websitesConfig, $storeData['website_id']); + if (isset($store['website_id'])) { + $websiteConfig = $this->getWebsiteConfig($websitesConfig, $store['website_id']); } $storeConfig = isset($storesConfig[$code]) ? $storesConfig[$code] : []; $result[$code] = array_replace_recursive($defaultConfig, $websiteConfig, $storeConfig); @@ -94,17 +161,17 @@ private function prepareStoresConfig(array $defaultConfig, array $websitesConfig } /** - * Retrieve Website Config + * Find information about website by its ID. * - * @param array $websites + * @param array $websites Has next format: (website_code => [website_data]) * @param int $id * @return array */ private function getWebsiteConfig(array $websites, $id) { - foreach ($this->scopes->get('websites') as $websiteData) { - if ($websiteData['website_id'] == $id) { - $code = $websiteData['code']; + foreach ((array)$this->websiteData as $website) { + if ($website['website_id'] == $id) { + $code = $website['code']; return isset($websites[$code]) ? $websites[$code] : []; } } diff --git a/app/code/Magento/Store/Model/GroupRepository.php b/app/code/Magento/Store/Model/GroupRepository.php index 80cc6f12c4de..da1b82c3bf68 100644 --- a/app/code/Magento/Store/Model/GroupRepository.php +++ b/app/code/Magento/Store/Model/GroupRepository.php @@ -62,18 +62,8 @@ public function get($id) return $this->entities[$id]; } - $groupData = []; - $groups = $this->getAppConfig()->get('scopes', 'groups', []); - if ($groups) { - foreach ($groups as $data) { - if (isset($data['group_id']) && $data['group_id'] == $id) { - $groupData = $data; - break; - } - } - } $group = $this->groupFactory->create([ - 'data' => $groupData + 'data' => $this->getAppConfig()->get('scopes', "groups/$id", []) ]); if (null === $group->getId()) { diff --git a/app/code/Magento/Store/Model/ResourceModel/Store.php b/app/code/Magento/Store/Model/ResourceModel/Store.php index 5e16fbd68ced..4446472a7117 100644 --- a/app/code/Magento/Store/Model/ResourceModel/Store.php +++ b/app/code/Magento/Store/Model/ResourceModel/Store.php @@ -157,6 +157,20 @@ protected function _changeGroup(\Magento\Framework\Model\AbstractModel $model) return $this; } + /** + * Read information about all stores + * + * @return array + */ + public function readAllStores() + { + $select = $this->getConnection() + ->select() + ->from($this->getTable('store')); + + return $this->getConnection()->fetchAll($select); + } + /** * Retrieve select object for load object data * diff --git a/app/code/Magento/Store/Model/ResourceModel/Website.php b/app/code/Magento/Store/Model/ResourceModel/Website.php index e8aa73728dc2..9cd52ca2eb98 100644 --- a/app/code/Magento/Store/Model/ResourceModel/Website.php +++ b/app/code/Magento/Store/Model/ResourceModel/Website.php @@ -36,6 +36,28 @@ protected function _initUniqueFields() return $this; } + /** + * Read all information about websites. + * + * Convert information to next format: + * [website_code => [website_data (website_id, code, name, etc...)]] + * + * @return array + */ + public function readAllWebsites() + { + $websites = []; + $select = $this->getConnection() + ->select() + ->from($this->getTable('store_website')); + + foreach($this->getConnection()->fetchAll($select) as $websiteData) { + $websites[$websiteData['code']] = $websiteData; + } + + return $websites; + } + /** * Validate website code before object save * diff --git a/app/code/Magento/Store/Model/StoreRepository.php b/app/code/Magento/Store/Model/StoreRepository.php index 2ed8860615f8..282dfb5b6708 100644 --- a/app/code/Magento/Store/Model/StoreRepository.php +++ b/app/code/Magento/Store/Model/StoreRepository.php @@ -100,14 +100,7 @@ public function getById($id) return $this->entitiesById[$id]; } - $storeData = []; - $stores = $this->getAppConfig()->get('scopes', "stores", []); - foreach ($stores as $data) { - if (isset($data['store_id']) && $data['store_id'] == $id) { - $storeData = $data; - break; - } - } + $storeData = $this->getAppConfig()->get('scopes', "stores/$id", []); $store = $this->storeFactory->create([ 'data' => $storeData ]); diff --git a/app/code/Magento/Store/Model/WebsiteRepository.php b/app/code/Magento/Store/Model/WebsiteRepository.php index 14bbe20b30d6..3b73ba4019a0 100644 --- a/app/code/Magento/Store/Model/WebsiteRepository.php +++ b/app/code/Magento/Store/Model/WebsiteRepository.php @@ -92,14 +92,8 @@ public function getById($id) if (isset($this->entitiesById[$id])) { return $this->entitiesById[$id]; } - $websiteData = []; - $websites = $this->getAppConfig()->get('scopes', 'websites', []); - foreach ($websites as $data) { - if (isset($data['website_id']) && $data['website_id'] == $id) { - $websiteData = $data; - break; - } - } + + $websiteData = $this->getAppConfig()->get('scopes', "websites/$id", []); $website = $this->factory->create([ 'data' => $websiteData ]); @@ -185,7 +179,7 @@ private function getAppConfig() */ private function initDefaultWebsite() { - $websites = (array)$this->getAppConfig()->get('scopes', 'websites', []); + $websites = (array) $this->getAppConfig()->get('scopes', 'websites', []); foreach ($websites as $data) { if (isset($data['is_default']) && $data['is_default'] == 1) { if ($this->default) { diff --git a/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php b/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php index 74d7a3e8b0eb..1617161fff36 100644 --- a/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php +++ b/app/code/Magento/Store/Test/Unit/App/Config/Source/RuntimeConfigSourceTest.php @@ -104,25 +104,22 @@ class RuntimeConfigSourceTest extends \PHPUnit_Framework_TestCase public function setUp() { $this->data = [ - 'group' => [ - 'code' => 'myGroup', - 'data' => [ + 'groups' => [ + '1' => [ 'name' => 'My Group', - 'group_id' => $this->data['group']['code'] - ] + 'group_id' => 1 + ], ], - 'website' => [ - 'code' => 'myWebsite', - 'data' => [ - 'name' => 'My Website', - 'website_code' => $this->data['website']['code'] + 'stores' => [ + 'myStore' => [ + 'name' => 'My Store', + 'code' => 'myStore' ] ], - 'store' => [ - 'code' => 'myStore', - 'data' => [ - 'name' => 'My Store', - 'store_code' => $this->data['store']['code'] + 'websites' => [ + 'myWebsite' => [ + 'name' => 'My Website', + 'code' => 'myWebsite' ] ], ]; @@ -209,26 +206,16 @@ private function getExpectedResult($path) { switch ($this->getScope($path)) { case 'websites': - $result = $this->data['website']['data']; + $result = ['websites' => $this->data['websites']]; break; case 'groups': - $result = $this->data['group']['data']; + $result = ['groups' => $this->data['groups']]; break; case 'stores': - $result = $this->data['store']['data']; + $result = ['stores' => $this->data['stores']]; break; default: - $result = [ - 'websites' => [ - $this->data['website']['code'] => $this->data['website']['data'] - ], - 'groups' => [ - $this->data['group']['code'] => $this->data['group']['data'] - ], - 'stores' => [ - $this->data['store']['code'] => $this->data['store']['data'] - ], - ]; + $result = $this->data; break; } return $result; @@ -244,7 +231,7 @@ private function prepareStores($path) ->willReturn($this->store); $this->store->expects($this->once()) ->method('load') - ->with($this->data['store']['code'], 'code') + ->with('myStore', 'code') ->willReturnSelf(); } else { $this->storeCollectionFactory->expects($this->once()) @@ -259,11 +246,11 @@ private function prepareStores($path) ->willReturn(new \ArrayIterator([$this->store])); $this->store->expects($this->once()) ->method('getCode') - ->willReturn($this->data['store']['code']); + ->willReturn('myStore'); } $this->store->expects($this->once()) ->method('getData') - ->willReturn($this->data['store']['data']); + ->willReturn($this->data['stores']['myStore']); } } @@ -277,7 +264,7 @@ private function prepareGroups($path) ->willReturn($this->group); $this->group->expects($this->once()) ->method('load') - ->with($this->data['group']['code']) + ->with($this->data['groups']['1']['group_id']) ->willReturnSelf(); } else { $this->groupCollectionFactory->expects($this->once()) @@ -292,11 +279,11 @@ private function prepareGroups($path) ->willReturn(new \ArrayIterator([$this->group])); $this->group->expects($this->once()) ->method('getId') - ->willReturn($this->data['group']['code']); + ->willReturn($this->data['groups']['1']['group_id']); } $this->group->expects($this->once()) ->method('getData') - ->willReturn($this->data['group']['data']); + ->willReturn($this->data['groups']['1']); } } @@ -310,7 +297,7 @@ private function prepareWebsites($path) ->willReturn($this->website); $this->website->expects($this->once()) ->method('load') - ->with($this->data['website']['code']) + ->with($this->data['websites']['myWebsite']['code']) ->willReturnSelf(); } else { $this->websiteCollectionFactory->expects($this->once()) @@ -325,11 +312,11 @@ private function prepareWebsites($path) ->willReturn(new \ArrayIterator([$this->website])); $this->website->expects($this->once()) ->method('getCode') - ->willReturn($this->data['website']['code']); + ->willReturn('myWebsite'); } $this->website->expects($this->once()) ->method('getData') - ->willReturn($this->data['website']['data']); + ->willReturn($this->data['websites']['myWebsite']); } } @@ -350,7 +337,7 @@ public function getDataProvider() { return [ ['websites/myWebsite'], - ['groups/myGroup'], + ['groups/1'], ['stores/myStore'], ['default'] ]; diff --git a/app/code/Magento/Swatches/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Swatches/view/adminhtml/ui_component/design_config_form.xml index 18b4246d58f3..66626add3f67 100644 --- a/app/code/Magento/Swatches/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Swatches/view/adminhtml/ui_component/design_config_form.xml @@ -22,7 +22,7 @@ - jpg jpeg gif png svg + jpg jpeg gif png 2097152 theme/design_config_fileUploader/save diff --git a/app/code/Magento/Tax/Setup/RecurringData.php b/app/code/Magento/Tax/Setup/RecurringData.php new file mode 100644 index 000000000000..bc05db428cde --- /dev/null +++ b/app/code/Magento/Tax/Setup/RecurringData.php @@ -0,0 +1,92 @@ +taxRateRepository = $taxRateRepository; + $this->searchCriteriaFactory = $searchCriteriaFactory; + $this->directoryRegionFactory = $directoryRegionFactory; + } + + /** + * {@inheritdoc} + */ + public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $taxRateList = $this->taxRateRepository->getList($this->searchCriteriaFactory->create()); + /** @var \Magento\Tax\Api\Data\TaxRateInterface $taxRateData */ + foreach ($taxRateList->getItems() as $taxRateData) { + $regionCode = $this->parseRegionFromTaxCode($taxRateData->getCode()); + if ($regionCode) { + /** @var \Magento\Directory\Model\Region $region */ + $region = $this->directoryRegionFactory->create(); + $region->loadByCode($regionCode, $taxRateData->getTaxCountryId()); + $taxRateData->setTaxRegionId($region->getRegionId()); + $this->taxRateRepository->save($taxRateData); + } + } + } + + /** + * Parse region code from tax code + * + * @param string $taxCode + * @return string + */ + private function parseRegionFromTaxCode($taxCode) + { + $result = ''; + $parts = explode('-', $taxCode, 3); + + if (isset($parts[1])) { + $result = $parts[1]; + } + + return $result; + } +} diff --git a/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php b/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php index a041fffbbb11..fc826daf19a6 100644 --- a/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php +++ b/app/code/Magento/Theme/Controller/Adminhtml/Design/Config/FileUploader/Save.php @@ -22,6 +22,11 @@ class Save extends Action */ protected $fileProcessor; + /** + * Authorization level + */ + const ADMIN_RESOURCE = 'Magento_Theme::theme'; + /** * @param Context $context * @param FileProcessor $fileProcessor diff --git a/app/code/Magento/Theme/Model/Design/Backend/Favicon.php b/app/code/Magento/Theme/Model/Design/Backend/Favicon.php index 160642818cfb..2d53b11eea6c 100644 --- a/app/code/Magento/Theme/Model/Design/Backend/Favicon.php +++ b/app/code/Magento/Theme/Model/Design/Backend/Favicon.php @@ -43,6 +43,6 @@ protected function _addWhetherScopeInfo() */ public function getAllowedExtensions() { - return ['ico', 'png', 'gif', 'jpg', 'jpeg', 'apng', 'svg']; + return ['ico', 'png', 'gif', 'jpg', 'jpeg', 'apng']; } } diff --git a/app/code/Magento/Theme/Model/Design/Backend/Logo.php b/app/code/Magento/Theme/Model/Design/Backend/Logo.php index 88a1317d74b7..e5652d7ee400 100644 --- a/app/code/Magento/Theme/Model/Design/Backend/Logo.php +++ b/app/code/Magento/Theme/Model/Design/Backend/Logo.php @@ -41,6 +41,6 @@ protected function _addWhetherScopeInfo() */ public function getAllowedExtensions() { - return ['jpg', 'jpeg', 'gif', 'png', 'svg']; + return ['jpg', 'jpeg', 'gif', 'png']; } } diff --git a/app/code/Magento/Theme/Model/View/Design.php b/app/code/Magento/Theme/Model/View/Design.php index 32481e0765a3..98f7665400dd 100644 --- a/app/code/Magento/Theme/Model/View/Design.php +++ b/app/code/Magento/Theme/Model/View/Design.php @@ -256,6 +256,16 @@ public function getLocale() return $this->_locale->getLocale(); } + /** + * @param \Magento\Framework\Locale\ResolverInterface $locale + * @return $this + */ + public function setLocale(\Magento\Framework\Locale\ResolverInterface $locale) + { + $this->_locale = $locale; + return $this; + } + /** * {@inheritdoc} */ diff --git a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml index c89c25e1a324..20625842a727 100644 --- a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml @@ -56,14 +56,14 @@ - Allowed file types: ico, png, gif, jpg, jpeg, apng, svg. Not all browsers support all these formats! + Allowed file types: ico, png, gif, jpg, jpeg, apng. Not all browsers support all these formats! fileUploader - jpg jpeg gif png svg ico apng + jpg jpeg gif png ico apng 2097152 theme/design_config_fileUploader/save @@ -153,14 +153,14 @@ - Allowed file types: png, gif, jpg, jpeg, svg. + Allowed file types: png, gif, jpg, jpeg. fileUploader - jpg jpeg gif png svg + jpg jpeg gif png 2097152 theme/design_config_fileUploader/save diff --git a/app/code/Magento/Ui/Component/Listing/Columns/Date.php b/app/code/Magento/Ui/Component/Listing/Columns/Date.php index 959bfe01f8eb..db8e5e19c738 100644 --- a/app/code/Magento/Ui/Component/Listing/Columns/Date.php +++ b/app/code/Magento/Ui/Component/Listing/Columns/Date.php @@ -57,7 +57,7 @@ public function prepareDataSource(array $dataSource) $date = $this->timezone->date(new \DateTime($item[$this->getData('name')])); $timezone = isset($this->getConfiguration()['timezone']) ? $this->booleanUtils->convert($this->getConfiguration()['timezone']) - : false; + : true; if (!$timezone) { $date = new \DateTime($item[$this->getData('name')]); } diff --git a/app/code/Magento/Ui/DataProvider/SearchResultFactory.php b/app/code/Magento/Ui/DataProvider/SearchResultFactory.php new file mode 100644 index 000000000000..3f54b06b6b19 --- /dev/null +++ b/app/code/Magento/Ui/DataProvider/SearchResultFactory.php @@ -0,0 +1,113 @@ +hydrator = $hydrator; + $this->documentFactory = $documentFactory; + $this->searchResultFactory = $searchResultFactory; + $this->attributeValueFactory = $attributeValueFactory; + } + + /** + * @param array $items + * @param int $totalCount + * @param SearchCriteriaInterface SearchCriteriaInterface $searchCriteria + * @param string $idFieldName + * @return SearchResultInterface + */ + public function create(array $items, $totalCount, SearchCriteriaInterface $searchCriteria, $idFieldName) + { + $documents = []; + foreach ($items as $item) { + $itemData = $this->hydrator->extract($item); + $itemId = $itemData[$idFieldName]; + $attributes = $this->createAttributes($idFieldName, $itemData); + + $document = $this->documentFactory->create(); + $document->setId($itemId); + $document->setCustomAttributes($attributes); + $documents[] = $document; + } + + $searchResult = $this->searchResultFactory->create(); + $searchResult->setItems($documents); + $searchResult->setTotalCount($totalCount); + $searchResult->setSearchCriteria($searchCriteria); + return $searchResult; + } + + /** + * @param string $idFieldName + * @param array $itemData + * @return array + */ + private function createAttributes($idFieldName, $itemData) + { + $attributes = []; + + $idFieldNameAttribute = $this->attributeValueFactory->create(); + $idFieldNameAttribute->setAttributeCode('id_field_name'); + $idFieldNameAttribute->setValue($idFieldName); + $attributes[] = $idFieldNameAttribute; + + foreach ($itemData as $key => $value) { + $attribute = $this->attributeValueFactory->create(); + $attribute->setAttributeCode($key); + if (is_bool($value)) { + // for proper work of form and grid (for example for Yes/No properties) + $value = (string)(int)$value; + } + $attribute->setValue($value); + $attributes[] = $attribute; + } + return $attributes; + } +} diff --git a/app/code/Magento/User/Setup/UpgradeSchema.php b/app/code/Magento/User/Setup/UpgradeSchema.php index e392eb52be0b..286ebd9d69c0 100644 --- a/app/code/Magento/User/Setup/UpgradeSchema.php +++ b/app/code/Magento/User/Setup/UpgradeSchema.php @@ -21,11 +21,27 @@ class UpgradeSchema implements UpgradeSchemaInterface */ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) { - $installer = $setup; - $installer->startSetup(); - $tableAdmins = $installer->getTable('admin_user'); + $setup->startSetup(); - $installer->getConnection()->addColumn( + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $this->addFailuresToAdminUserTable($setup); + $this->createAdminPasswordsTable($setup); + } + + $setup->endSetup(); + } + + /** + * Adds 'failures_num', 'first_failure', and 'lock_expires' columns to 'admin_user' table + * + * @param SchemaSetupInterface $setup + * @return void + */ + private function addFailuresToAdminUserTable(SchemaSetupInterface $setup) + { + $tableAdmins = $setup->getTable('admin_user'); + + $setup->getConnection()->addColumn( $tableAdmins, 'failures_num', [ @@ -35,7 +51,7 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con 'comment' => 'Failure Number' ] ); - $installer->getConnection()->addColumn( + $setup->getConnection()->addColumn( $tableAdmins, 'first_failure', [ @@ -43,7 +59,7 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con 'comment' => 'First Failure' ] ); - $installer->getConnection()->addColumn( + $setup->getConnection()->addColumn( $tableAdmins, 'lock_expires', [ @@ -51,12 +67,22 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con 'comment' => 'Expiration Lock Dates' ] ); + } + + /** + * Create table 'admin_passwords' + * + * @param SchemaSetupInterface $setup + * @return void + */ + private function createAdminPasswordsTable(SchemaSetupInterface $setup) + { + if ($setup->tableExists($setup->getTable('admin_passwords'))) { + return; + } - /** - * Create table 'admin_passwords' - */ - $table = $installer->getConnection()->newTable( - $installer->getTable('admin_passwords') + $table = $setup->getConnection()->newTable( + $setup->getTable('admin_passwords') )->addColumn( 'password_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, @@ -88,19 +114,17 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Last Updated' )->addIndex( - $installer->getIdxName('admin_passwords', ['user_id']), + $setup->getIdxName('admin_passwords', ['user_id']), ['user_id'] )->addForeignKey( - $installer->getFkName('admin_passwords', 'user_id', 'admin_user', 'user_id'), + $setup->getFkName('admin_passwords', 'user_id', 'admin_user', 'user_id'), 'user_id', - $installer->getTable('admin_user'), + $setup->getTable('admin_user'), 'user_id', \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE )->setComment( 'Admin Passwords' ); - $installer->getConnection()->createTable($table); - - $installer->endSetup(); + $setup->getConnection()->createTable($table); } } diff --git a/app/code/Magento/Vault/Api/Data/PaymentTokenFactoryInterface.php b/app/code/Magento/Vault/Api/Data/PaymentTokenFactoryInterface.php new file mode 100644 index 000000000000..9b9ec22e1c7f --- /dev/null +++ b/app/code/Magento/Vault/Api/Data/PaymentTokenFactoryInterface.php @@ -0,0 +1,28 @@ +get(PaymentTokenFactoryInterface::class); + } + $this->objectManager = $objectManager; + $this->paymentTokenFactory = $paymentTokenFactory; } /** @@ -35,9 +50,6 @@ public function __construct(ObjectManagerInterface $objectManager) */ public function create() { - /** @var PaymentTokenInterface $paymentToken */ - $paymentToken = $this->objectManager->create(PaymentTokenInterface::class); - $paymentToken->setType($this->getType()); - return $paymentToken; + return $this->paymentTokenFactory->create($this->getType()); } } diff --git a/app/code/Magento/Vault/Model/AccountPaymentTokenFactory.php b/app/code/Magento/Vault/Model/AccountPaymentTokenFactory.php index b14a1e649ff1..5473e58d3cbb 100644 --- a/app/code/Magento/Vault/Model/AccountPaymentTokenFactory.php +++ b/app/code/Magento/Vault/Model/AccountPaymentTokenFactory.php @@ -7,7 +7,8 @@ /** * Class AccountPaymentTokenFactory - * @api + * @deprecated + * @see PaymentTokenFactoryInterface */ class AccountPaymentTokenFactory extends AbstractPaymentTokenFactory { diff --git a/app/code/Magento/Vault/Model/CreditCardTokenFactory.php b/app/code/Magento/Vault/Model/CreditCardTokenFactory.php index 9ae165ec3c8d..1f06f6bd36d7 100644 --- a/app/code/Magento/Vault/Model/CreditCardTokenFactory.php +++ b/app/code/Magento/Vault/Model/CreditCardTokenFactory.php @@ -7,7 +7,8 @@ /** * Class CreditCardTokenFactory - * @api + * @deprecated + * @see PaymentTokenFactoryInterface */ class CreditCardTokenFactory extends AbstractPaymentTokenFactory { diff --git a/app/code/Magento/Vault/Model/PaymentTokenFactory.php b/app/code/Magento/Vault/Model/PaymentTokenFactory.php new file mode 100644 index 000000000000..b1cbe9b28574 --- /dev/null +++ b/app/code/Magento/Vault/Model/PaymentTokenFactory.php @@ -0,0 +1,62 @@ +objectManager = $objectManager; + $this->tokenTypes = $tokenTypes; + } + + /** + * Create payment token entity + * @param $type string + * @return PaymentTokenInterface + */ + public function create($type = null) + { + /** + * This code added for Backward Compatibility reasons only, as previous implementation of Code Generated factory + * accepted an array as any other code generated factory + */ + if (is_array($type)) { + return $this->objectManager->create( + PaymentTokenInterface::class, + $type + ); + } + + if ($type !== null && !in_array($type, $this->tokenTypes, true)) { + throw new \LogicException('There is no such payment token type: ' . $type); + } + + return $this->objectManager->create( + PaymentTokenInterface::class, + ['data' => [PaymentTokenInterface::TYPE => $type]] + ); + } +} diff --git a/app/code/Magento/Vault/Test/Unit/Model/AccountPaymentTokenFactoryTest.php b/app/code/Magento/Vault/Test/Unit/Model/AccountPaymentTokenFactoryTest.php index 70988b4297a8..9e93bdd94877 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/AccountPaymentTokenFactoryTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/AccountPaymentTokenFactoryTest.php @@ -8,6 +8,7 @@ use Magento\Framework\ObjectManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Model\PaymentTokenFactory; use Magento\Vault\Model\AccountPaymentTokenFactory; use Magento\Vault\Model\PaymentToken; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -36,10 +37,16 @@ protected function setUp() { $objectManager = new ObjectManager($this); - $this->paymentToken = $objectManager->getObject(PaymentToken::class); + $tokenTypes = [ + 'account' => \Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT, + 'credit_card' => \Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD + ]; + $this->paymentToken = $objectManager->getObject(PaymentToken::class); $this->objectManager = $this->getMock(ObjectManagerInterface::class); - $this->factory = new AccountPaymentTokenFactory($this->objectManager); + + $this->paymentTokenFactory = new PaymentTokenFactory($this->objectManager, $tokenTypes); + $this->factory = new AccountPaymentTokenFactory($this->objectManager, $this->paymentTokenFactory); } /** @@ -51,6 +58,8 @@ public function testCreate() ->method('create') ->willReturn($this->paymentToken); + $this->paymentToken->setType(\Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT); + /** @var PaymentTokenInterface $paymentToken */ $paymentToken = $this->factory->create(); static::assertInstanceOf(PaymentTokenInterface::class, $paymentToken); diff --git a/app/code/Magento/Vault/Test/Unit/Model/CreditCardTokenFactoryTest.php b/app/code/Magento/Vault/Test/Unit/Model/CreditCardTokenFactoryTest.php index 65cd50442f5b..7ec33ac6f7f3 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/CreditCardTokenFactoryTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/CreditCardTokenFactoryTest.php @@ -8,6 +8,7 @@ use Magento\Framework\ObjectManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Model\PaymentTokenFactory; use Magento\Vault\Model\CreditCardTokenFactory; use Magento\Vault\Model\PaymentToken; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -36,10 +37,16 @@ protected function setUp() { $objectManager = new ObjectManager($this); - $this->paymentToken = $objectManager->getObject(PaymentToken::class); + $tokenTypes = [ + 'account' => \Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT, + 'credit_card' => \Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD + ]; + $this->paymentToken = $objectManager->getObject(PaymentToken::class); $this->objectManager = $this->getMock(ObjectManagerInterface::class); - $this->factory = new CreditCardTokenFactory($this->objectManager); + + $this->paymentTokenFactory = new PaymentTokenFactory($this->objectManager, $tokenTypes); + $this->factory = new CreditCardTokenFactory($this->objectManager, $this->paymentTokenFactory); } /** @@ -51,6 +58,8 @@ public function testCreate() ->method('create') ->willReturn($this->paymentToken); + $this->paymentToken->setType(\Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD); + /** @var PaymentTokenInterface $paymentToken */ $paymentToken = $this->factory->create(); static::assertInstanceOf(PaymentTokenInterface::class, $paymentToken); diff --git a/app/code/Magento/Vault/etc/di.xml b/app/code/Magento/Vault/etc/di.xml index 9d2befe96667..95191e441757 100644 --- a/app/code/Magento/Vault/etc/di.xml +++ b/app/code/Magento/Vault/etc/di.xml @@ -9,6 +9,7 @@ + @@ -43,4 +44,13 @@ + + + + + Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_ACCOUNT + Magento\Vault\Api\Data\PaymentTokenFactoryInterface::TOKEN_TYPE_CREDIT_CARD + + + diff --git a/app/design/frontend/Magento/blank/Magento_ProductVideo/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_ProductVideo/web/css/source/_module.less index f2fae8eca917..023c53154596 100644 --- a/app/design/frontend/Magento/blank/Magento_ProductVideo/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_ProductVideo/web/css/source/_module.less @@ -83,6 +83,12 @@ top: 12px; width: 100px; } + + .fotorama__product-video--loading { + &:after { + visibility: hidden; + } + } } // diff --git a/composer.json b/composer.json index 083a4918a5c7..79012b5f11db 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ "monolog/monolog": "^1.17", "oyejorge/less.php": "~1.7.0", "pelago/emogrifier": "0.1.1", - "tubalmartin/cssmin": "3.0.0", + "tubalmartin/cssmin": "4.1.0", "magento/magento-composer-installer": ">=0.1.11", "braintree/braintree_php": "3.22.0", "symfony/console": "~2.3, !=2.7.0", @@ -68,6 +68,7 @@ "ext-openssl": "*", "ext-zip": "*", "ext-pdo_mysql": "*", + "ext-soap": "*", "sjparkinson/static-review": "~4.1", "ramsey/uuid": "3.6.1" }, @@ -98,7 +99,6 @@ "magento/module-catalog-analytics": "100.2.0-dev", "magento/module-catalog-import-export": "100.2.0-dev", "magento/module-catalog-inventory": "100.2.0-dev", - "magento/module-catalog-inventory-configurable-product": "100.2.0-dev", "magento/module-catalog-rule": "100.2.0-dev", "magento/module-catalog-rule-configurable": "100.2.0-dev", "magento/module-catalog-search": "100.2.0-dev", @@ -137,6 +137,8 @@ "magento/module-import-export": "100.2.0-dev", "magento/module-indexer": "100.2.0-dev", "magento/module-integration": "100.2.0-dev", + "magento/module-inventory": "100.0.0-dev", + "magento/module-inventory-api": "100.0.0-dev", "magento/module-layered-navigation": "100.2.0-dev", "magento/module-media-storage": "100.2.0-dev", "magento/module-msrp": "100.2.0-dev", diff --git a/composer.lock b/composer.lock index 119ad0b8059c..7ccbfcb6874b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "468277cb85ede262e3dd3155da88a15a", - "content-hash": "83b07861c465af490d6cf0c75adc78fd", + "content-hash": "aa276f67ca493a4ad67bd7c45b69c4c8", "packages": [ { "name": "braintree/braintree_php", @@ -52,7 +51,7 @@ } ], "description": "Braintree PHP Client Library", - "time": "2017-02-16 19:59:04" + "time": "2017-02-16T19:59:04+00:00" }, { "name": "colinmollenhour/cache-backend-file", @@ -88,7 +87,7 @@ ], "description": "The stock Zend_Cache_Backend_File backend has extremely poor performance for cleaning by tags making it become unusable as the number of cached items increases. This backend makes many changes resulting in a huge performance boost, especially for tag cleaning.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_File", - "time": "2016-05-02 16:24:47" + "time": "2016-05-02T16:24:47+00:00" }, { "name": "colinmollenhour/cache-backend-redis", @@ -124,7 +123,7 @@ ], "description": "Zend_Cache backend using Redis with full support for tags.", "homepage": "https://github.com/colinmollenhour/Cm_Cache_Backend_Redis", - "time": "2017-03-25 04:54:24" + "time": "2017-03-25T04:54:24+00:00" }, { "name": "colinmollenhour/credis", @@ -163,7 +162,7 @@ ], "description": "Credis is a lightweight interface to the Redis key-value store which wraps the phpredis library when available for better performance.", "homepage": "https://github.com/colinmollenhour/credis", - "time": "2015-11-28 01:20:04" + "time": "2015-11-28T01:20:04+00:00" }, { "name": "colinmollenhour/php-redis-session-abstract", @@ -200,7 +199,7 @@ ], "description": "A Redis-based session handler with optimistic locking", "homepage": "https://github.com/colinmollenhour/php-redis-session-abstract", - "time": "2017-04-19 14:21:43" + "time": "2017-04-19T14:21:43+00:00" }, { "name": "composer/ca-bundle", @@ -259,7 +258,7 @@ "ssl", "tls" ], - "time": "2017-03-06 11:59:08" + "time": "2017-03-06T11:59:08+00:00" }, { "name": "composer/composer", @@ -336,7 +335,7 @@ "dependency", "package" ], - "time": "2017-03-10 08:29:45" + "time": "2017-03-10T08:29:45+00:00" }, { "name": "composer/semver", @@ -398,7 +397,7 @@ "validation", "versioning" ], - "time": "2016-08-30 16:08:34" + "time": "2016-08-30T16:08:34+00:00" }, { "name": "composer/spdx-licenses", @@ -459,7 +458,7 @@ "spdx", "validator" ], - "time": "2017-04-03 19:08:52" + "time": "2017-04-03T19:08:52+00:00" }, { "name": "container-interop/container-interop", @@ -490,20 +489,20 @@ ], "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "homepage": "https://github.com/container-interop/container-interop", - "time": "2017-02-14 19:40:03" + "time": "2017-02-14T19:40:03+00:00" }, { "name": "justinrainbow/json-schema", - "version": "5.2.0", + "version": "5.2.1", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "e3c9bccdc38bbd09bcac0131c00f3be58368b416" + "reference": "429be236f296ca249d61c65649cdf2652f4a5e80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/e3c9bccdc38bbd09bcac0131c00f3be58368b416", - "reference": "e3c9bccdc38bbd09bcac0131c00f3be58368b416", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/429be236f296ca249d61c65649cdf2652f4a5e80", + "reference": "429be236f296ca249d61c65649cdf2652f4a5e80", "shasum": "" }, "require": { @@ -512,7 +511,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.1", "json-schema/json-schema-test-suite": "1.2.0", - "phpdocumentor/phpdocumentor": "~2", + "phpdocumentor/phpdocumentor": "^2.7", "phpunit/phpunit": "^4.8.22" }, "bin": [ @@ -557,7 +556,7 @@ "json", "schema" ], - "time": "2017-03-22 22:43:35" + "time": "2017-05-16T21:06:09+00:00" }, { "name": "league/climate", @@ -606,7 +605,7 @@ "php", "terminal" ], - "time": "2015-01-18 14:31:58" + "time": "2015-01-18T14:31:58+00:00" }, { "name": "magento/composer", @@ -642,7 +641,7 @@ "AFL-3.0" ], "description": "Magento composer library helps to instantiate Composer application and run composer commands.", - "time": "2017-04-24 09:57:02" + "time": "2017-04-24T09:57:02+00:00" }, { "name": "magento/magento-composer-installer", @@ -721,7 +720,7 @@ "composer-installer", "magento" ], - "time": "2016-10-06 16:05:07" + "time": "2016-10-06T16:05:07+00:00" }, { "name": "magento/zendframework1", @@ -768,20 +767,20 @@ "ZF1", "framework" ], - "time": "2017-04-24 09:56:59" + "time": "2017-04-24T09:56:59+00:00" }, { "name": "monolog/monolog", - "version": "1.22.1", + "version": "1.23.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0" + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/1e044bc4b34e91743943479f1be7a1d5eb93add0", - "reference": "1e044bc4b34e91743943479f1be7a1d5eb93add0", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", "shasum": "" }, "require": { @@ -802,7 +801,7 @@ "phpunit/phpunit-mock-objects": "2.3.0", "ruflin/elastica": ">=0.90 <3.0", "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "~5.3" + "swiftmailer/swiftmailer": "^5.3|^6.0" }, "suggest": { "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", @@ -846,7 +845,7 @@ "logging", "psr-3" ], - "time": "2017-03-13 07:08:03" + "time": "2017-06-19T01:22:40+00:00" }, { "name": "oyejorge/less.php", @@ -908,7 +907,7 @@ "php", "stylesheet" ], - "time": "2017-03-28 22:19:25" + "time": "2017-03-28T22:19:25+00:00" }, { "name": "paragonie/random_compat", @@ -956,7 +955,7 @@ "pseudorandom", "random" ], - "time": "2017-03-13 16:27:32" + "time": "2017-03-13T16:27:32+00:00" }, { "name": "pelago/emogrifier", @@ -1012,20 +1011,20 @@ ], "description": "Converts CSS styles into inline style attributes in your HTML code", "homepage": "http://www.pelagodesign.com/sidecar/emogrifier/", - "time": "2015-05-15 11:37:51" + "time": "2015-05-15T11:37:51+00:00" }, { "name": "phpseclib/phpseclib", - "version": "2.0.4", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "ab8028c93c03cc8d9c824efa75dc94f1db2369bf" + "reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/ab8028c93c03cc8d9c824efa75dc94f1db2369bf", - "reference": "ab8028c93c03cc8d9c824efa75dc94f1db2369bf", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/34a7699e6f31b1ef4035ee36444407cecf9f56aa", + "reference": "34a7699e6f31b1ef4035ee36444407cecf9f56aa", "shasum": "" }, "require": { @@ -1104,7 +1103,7 @@ "x.509", "x509" ], - "time": "2016-10-04 00:57:04" + "time": "2017-06-05T06:31:10+00:00" }, { "name": "psr/container", @@ -1153,7 +1152,7 @@ "container-interop", "psr" ], - "time": "2017-02-14 16:28:37" + "time": "2017-02-14T16:28:37+00:00" }, { "name": "psr/log", @@ -1200,7 +1199,7 @@ "psr", "psr-3" ], - "time": "2016-10-10 12:19:37" + "time": "2016-10-10T12:19:37+00:00" }, { "name": "ramsey/uuid", @@ -1282,7 +1281,7 @@ "identifier", "uuid" ], - "time": "2017-03-26 20:37:53" + "time": "2017-03-26T20:37:53+00:00" }, { "name": "seld/cli-prompt", @@ -1330,20 +1329,20 @@ "input", "prompt" ], - "time": "2017-03-18 11:32:45" + "time": "2017-03-18T11:32:45+00:00" }, { "name": "seld/jsonlint", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/Seldaek/jsonlint.git", - "reference": "791f8c594f300d246cdf01c6b3e1e19611e301d8" + "reference": "50d63f2858d87c4738d5b76a7dcbb99fa8cf7c77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/791f8c594f300d246cdf01c6b3e1e19611e301d8", - "reference": "791f8c594f300d246cdf01c6b3e1e19611e301d8", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/50d63f2858d87c4738d5b76a7dcbb99fa8cf7c77", + "reference": "50d63f2858d87c4738d5b76a7dcbb99fa8cf7c77", "shasum": "" }, "require": { @@ -1379,7 +1378,7 @@ "parser", "validator" ], - "time": "2017-03-06 16:42:24" + "time": "2017-06-18T15:11:04+00:00" }, { "name": "seld/phar-utils", @@ -1423,7 +1422,7 @@ "keywords": [ "phra" ], - "time": "2015-10-13 18:44:15" + "time": "2015-10-13T18:44:15+00:00" }, { "name": "sjparkinson/static-review", @@ -1476,20 +1475,20 @@ } ], "description": "An extendable framework for version control hooks.", - "time": "2014-09-22 08:40:36" + "time": "2014-09-22T08:40:36+00:00" }, { "name": "symfony/console", - "version": "v2.8.20", + "version": "v2.8.22", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2cfcbced8e39e2313ed4da8896fc8c59a56c0d7e" + "reference": "3ef6ef64abecd566d551d9e7f6393ac6e93b2462" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2cfcbced8e39e2313ed4da8896fc8c59a56c0d7e", - "reference": "2cfcbced8e39e2313ed4da8896fc8c59a56c0d7e", + "url": "https://api.github.com/repos/symfony/console/zipball/3ef6ef64abecd566d551d9e7f6393ac6e93b2462", + "reference": "3ef6ef64abecd566d551d9e7f6393ac6e93b2462", "shasum": "" }, "require": { @@ -1537,7 +1536,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2017-04-26 01:38:53" + "time": "2017-06-02T14:36:56+00:00" }, { "name": "symfony/debug", @@ -1594,20 +1593,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2016-07-30 07:22:48" + "time": "2016-07-30T07:22:48+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v2.8.20", + "version": "v2.8.22", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "7fc8e2b4118ff316550596357325dfd92a51f531" + "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/7fc8e2b4118ff316550596357325dfd92a51f531", - "reference": "7fc8e2b4118ff316550596357325dfd92a51f531", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/1377400fd641d7d1935981546aaef780ecd5bf6d", + "reference": "1377400fd641d7d1935981546aaef780ecd5bf6d", "shasum": "" }, "require": { @@ -1654,20 +1653,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-04-26 16:56:54" + "time": "2017-06-02T07:47:27+00:00" }, { "name": "symfony/filesystem", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "040651db13cf061827a460cc10f6e36a445c45b4" + "reference": "c709670bf64721202ddbe4162846f250735842c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/040651db13cf061827a460cc10f6e36a445c45b4", - "reference": "040651db13cf061827a460cc10f6e36a445c45b4", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/c709670bf64721202ddbe4162846f250735842c0", + "reference": "c709670bf64721202ddbe4162846f250735842c0", "shasum": "" }, "require": { @@ -1676,7 +1675,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1703,20 +1702,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2017-04-12 14:13:17" + "time": "2017-05-28T14:08:56+00:00" }, { "name": "symfony/finder", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930" + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", - "reference": "9cf076f8f492f4b1ffac40aae9c2d287b4ca6930", + "url": "https://api.github.com/repos/symfony/finder/zipball/baea7f66d30854ad32988c11a09d7ffd485810c4", + "reference": "baea7f66d30854ad32988c11a09d7ffd485810c4", "shasum": "" }, "require": { @@ -1725,7 +1724,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -1752,20 +1751,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2017-04-12 14:13:17" + "time": "2017-06-01T21:01:25+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" + "reference": "f29dca382a6485c3cbe6379f0c61230167681937" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", + "reference": "f29dca382a6485c3cbe6379f0c61230167681937", "shasum": "" }, "require": { @@ -1777,7 +1776,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -1811,20 +1810,20 @@ "portable", "shim" ], - "time": "2016-11-14 01:06:16" + "time": "2017-06-09T14:24:12+00:00" }, { "name": "symfony/process", - "version": "v2.8.20", + "version": "v2.8.22", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "aff35fb3dee799c84a7313c576b72208b046ef8d" + "reference": "d54232f5682fda2f8bbebff7c81b864646867ab9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/aff35fb3dee799c84a7313c576b72208b046ef8d", - "reference": "aff35fb3dee799c84a7313c576b72208b046ef8d", + "url": "https://api.github.com/repos/symfony/process/zipball/d54232f5682fda2f8bbebff7c81b864646867ab9", + "reference": "d54232f5682fda2f8bbebff7c81b864646867ab9", "shasum": "" }, "require": { @@ -1860,7 +1859,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-04-12 14:07:15" + "time": "2017-05-08T01:19:21+00:00" }, { "name": "tedivm/jshrink", @@ -1906,28 +1905,33 @@ "javascript", "minifier" ], - "time": "2015-07-04 07:35:09" + "time": "2015-07-04T07:35:09+00:00" }, { "name": "tubalmartin/cssmin", - "version": "v3.0.0", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", - "reference": "210b058bf9b3b43516e2c80e6db526c1ce215b04" + "reference": "1c7ae93cf6b392d4dae5c4ae18979918413af16e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/210b058bf9b3b43516e2c80e6db526c1ce215b04", - "reference": "210b058bf9b3b43516e2c80e6db526c1ce215b04", + "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/1c7ae93cf6b392d4dae5c4ae18979918413af16e", + "reference": "1c7ae93cf6b392d4dae5c4ae18979918413af16e", "shasum": "" }, "require": { + "ext-pcre": "*", "php": ">=5.3.2" }, "require-dev": { - "cogpowered/finediff": "0.3.*" + "cogpowered/finediff": "0.3.*", + "phpunit/phpunit": "4.8.*" }, + "bin": [ + "cssmin" + ], "type": "library", "autoload": { "psr-4": { @@ -1954,7 +1958,7 @@ "minify", "yui" ], - "time": "2017-04-04 14:33:00" + "time": "2017-05-16T13:45:26+00:00" }, { "name": "zendframework/zend-captcha", @@ -2011,7 +2015,7 @@ "captcha", "zf2" ], - "time": "2017-02-23 08:09:44" + "time": "2017-02-23T08:09:44+00:00" }, { "name": "zendframework/zend-code", @@ -2064,7 +2068,7 @@ "code", "zf2" ], - "time": "2016-10-24 13:23:32" + "time": "2016-10-24T13:23:32+00:00" }, { "name": "zendframework/zend-config", @@ -2120,7 +2124,7 @@ "config", "zf2" ], - "time": "2016-02-04 23:01:10" + "time": "2016-02-04T23:01:10+00:00" }, { "name": "zendframework/zend-console", @@ -2172,7 +2176,7 @@ "console", "zf2" ], - "time": "2016-02-09 17:15:12" + "time": "2016-02-09T17:15:12+00:00" }, { "name": "zendframework/zend-crypt", @@ -2222,7 +2226,7 @@ "crypt", "zf2" ], - "time": "2016-02-03 23:46:30" + "time": "2016-02-03T23:46:30+00:00" }, { "name": "zendframework/zend-db", @@ -2279,7 +2283,7 @@ "db", "zf2" ], - "time": "2016-08-09 19:28:55" + "time": "2016-08-09T19:28:55+00:00" }, { "name": "zendframework/zend-di", @@ -2326,7 +2330,7 @@ "di", "zf2" ], - "time": "2016-04-25 20:58:11" + "time": "2016-04-25T20:58:11+00:00" }, { "name": "zendframework/zend-escaper", @@ -2370,7 +2374,7 @@ "escaper", "zf2" ], - "time": "2016-06-30 19:48:38" + "time": "2016-06-30T19:48:38+00:00" }, { "name": "zendframework/zend-eventmanager", @@ -2417,20 +2421,20 @@ "eventmanager", "zf2" ], - "time": "2016-02-18 20:49:05" + "time": "2016-02-18T20:49:05+00:00" }, { "name": "zendframework/zend-filter", - "version": "2.7.1", + "version": "2.7.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "84c50246428efb0a1e52868e162dab3e149d5b80" + "reference": "b8d0ff872f126631bf63a932e33aa2d22d467175" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/84c50246428efb0a1e52868e162dab3e149d5b80", - "reference": "84c50246428efb0a1e52868e162dab3e149d5b80", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/b8d0ff872f126631bf63a932e33aa2d22d467175", + "reference": "b8d0ff872f126631bf63a932e33aa2d22d467175", "shasum": "" }, "require": { @@ -2438,10 +2442,10 @@ "zendframework/zend-stdlib": "^2.7 || ^3.0" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", "pear/archive_tar": "^1.4", - "phpunit/phpunit": "~4.0", - "zendframework/zend-crypt": "^2.6", + "phpunit/phpunit": "^6.0.10 || ^5.7.17", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-crypt": "^2.6 || ^3.0", "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3", "zendframework/zend-uri": "^2.5" }, @@ -2477,20 +2481,20 @@ "filter", "zf2" ], - "time": "2016-04-18 18:32:43" + "time": "2017-05-17T20:56:17+00:00" }, { "name": "zendframework/zend-form", - "version": "2.10.1", + "version": "2.10.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-form.git", - "reference": "1ca3ab771abd533d440646ec1cc082fccad976a1" + "reference": "252db729887844025772bb8045f8df605850ed9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-form/zipball/1ca3ab771abd533d440646ec1cc082fccad976a1", - "reference": "1ca3ab771abd533d440646ec1cc082fccad976a1", + "url": "https://api.github.com/repos/zendframework/zend-form/zipball/252db729887844025772bb8045f8df605850ed9c", + "reference": "252db729887844025772bb8045f8df605850ed9c", "shasum": "" }, "require": { @@ -2554,7 +2558,7 @@ "form", "zf2" ], - "time": "2017-04-26 21:27:43" + "time": "2017-05-18T14:59:53+00:00" }, { "name": "zendframework/zend-http", @@ -2604,7 +2608,7 @@ "http", "zf2" ], - "time": "2017-01-31 14:41:02" + "time": "2017-01-31T14:41:02+00:00" }, { "name": "zendframework/zend-hydrator", @@ -2662,30 +2666,30 @@ "hydrator", "zf2" ], - "time": "2016-02-18 22:38:26" + "time": "2016-02-18T22:38:26+00:00" }, { "name": "zendframework/zend-i18n", - "version": "2.7.3", + "version": "2.7.4", "source": { "type": "git", "url": "https://github.com/zendframework/zend-i18n.git", - "reference": "b2db0d8246a865c659f93199f90f5fc2cd2f3cd8" + "reference": "d3431e29cc00c2a1c6704e601d4371dbf24f6a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/b2db0d8246a865c659f93199f90f5fc2cd2f3cd8", - "reference": "b2db0d8246a865c659f93199f90f5fc2cd2f3cd8", + "url": "https://api.github.com/repos/zendframework/zend-i18n/zipball/d3431e29cc00c2a1c6704e601d4371dbf24f6a31", + "reference": "d3431e29cc00c2a1c6704e601d4371dbf24f6a31", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", + "php": "^7.0 || ^5.6", "zendframework/zend-stdlib": "^2.7 || ^3.0" }, "require-dev": { - "fabpot/php-cs-fixer": "1.7.*", - "phpunit/phpunit": "~4.0", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", "zendframework/zend-cache": "^2.6.1", + "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-config": "^2.6", "zendframework/zend-eventmanager": "^2.6.2 || ^3.0", "zendframework/zend-filter": "^2.6.1", @@ -2729,31 +2733,31 @@ "i18n", "zf2" ], - "time": "2016-06-07 21:08:30" + "time": "2017-05-17T17:00:12+00:00" }, { "name": "zendframework/zend-inputfilter", - "version": "2.7.3", + "version": "2.7.4", "source": { "type": "git", "url": "https://github.com/zendframework/zend-inputfilter.git", - "reference": "0cf1bdcd8858a8583965310a7dae63ad75bd1237" + "reference": "699ab4916e0aa73104e1f9ff068ef6d33c5f5fe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/0cf1bdcd8858a8583965310a7dae63ad75bd1237", - "reference": "0cf1bdcd8858a8583965310a7dae63ad75bd1237", + "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/699ab4916e0aa73104e1f9ff068ef6d33c5f5fe4", + "reference": "699ab4916e0aa73104e1f9ff068ef6d33c5f5fe4", "shasum": "" }, "require": { - "php": "^5.5 || ^7.0", + "php": "^7.0 || ^5.6", "zendframework/zend-filter": "^2.6", "zendframework/zend-stdlib": "^2.7 || ^3.0", "zendframework/zend-validator": "^2.6" }, "require-dev": { - "phpunit/phpunit": "^4.8", - "squizlabs/php_codesniffer": "^2.6.2", + "phpunit/phpunit": "^6.0.8 || ^5.7.15", + "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3" }, "suggest": { @@ -2784,7 +2788,7 @@ "inputfilter", "zf2" ], - "time": "2016-08-18 18:40:34" + "time": "2017-05-18T14:20:56+00:00" }, { "name": "zendframework/zend-json", @@ -2839,7 +2843,7 @@ "json", "zf2" ], - "time": "2016-02-04 21:20:26" + "time": "2016-02-04T21:20:26+00:00" }, { "name": "zendframework/zend-loader", @@ -2883,20 +2887,20 @@ "loader", "zf2" ], - "time": "2015-06-03 14:05:47" + "time": "2015-06-03T14:05:47+00:00" }, { "name": "zendframework/zend-log", - "version": "2.9.1", + "version": "2.9.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-log.git", - "reference": "115d75db1f8fb29efbf1b9a49cb91c662b7195dc" + "reference": "bf7489578d092d6ff7508117d1d920a4764fbd6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-log/zipball/115d75db1f8fb29efbf1b9a49cb91c662b7195dc", - "reference": "115d75db1f8fb29efbf1b9a49cb91c662b7195dc", + "url": "https://api.github.com/repos/zendframework/zend-log/zipball/bf7489578d092d6ff7508117d1d920a4764fbd6a", + "reference": "bf7489578d092d6ff7508117d1d920a4764fbd6a", "shasum": "" }, "require": { @@ -2909,9 +2913,9 @@ "psr/log-implementation": "1.0.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~1.7.0", "mikey179/vfsstream": "^1.6", - "phpunit/phpunit": "~4.0", + "phpunit/phpunit": "^5.7.15 || ^6.0.8", + "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-db": "^2.6", "zendframework/zend-escaper": "^2.5", "zendframework/zend-filter": "^2.5", @@ -2954,7 +2958,7 @@ "logging", "zf2" ], - "time": "2016-08-11 13:44:10" + "time": "2017-05-17T16:03:26+00:00" }, { "name": "zendframework/zend-math", @@ -3004,7 +3008,7 @@ "math", "zf2" ], - "time": "2016-04-07 16:29:53" + "time": "2016-04-07T16:29:53+00:00" }, { "name": "zendframework/zend-modulemanager", @@ -3063,7 +3067,7 @@ "modulemanager", "zf2" ], - "time": "2016-05-16 21:21:11" + "time": "2016-05-16T21:21:11+00:00" }, { "name": "zendframework/zend-mvc", @@ -3150,7 +3154,7 @@ "mvc", "zf2" ], - "time": "2016-02-23 15:24:59" + "time": "2016-02-23T15:24:59+00:00" }, { "name": "zendframework/zend-serializer", @@ -3207,7 +3211,7 @@ "serializer", "zf2" ], - "time": "2016-06-21 17:01:55" + "time": "2016-06-21T17:01:55+00:00" }, { "name": "zendframework/zend-server", @@ -3253,7 +3257,7 @@ "server", "zf2" ], - "time": "2016-06-20 22:27:55" + "time": "2016-06-20T22:27:55+00:00" }, { "name": "zendframework/zend-servicemanager", @@ -3305,7 +3309,7 @@ "servicemanager", "zf2" ], - "time": "2016-12-19 19:14:29" + "time": "2016-12-19T19:14:29+00:00" }, { "name": "zendframework/zend-session", @@ -3371,7 +3375,7 @@ "session", "zf2" ], - "time": "2016-07-05 18:32:50" + "time": "2016-07-05T18:32:50+00:00" }, { "name": "zendframework/zend-soap", @@ -3423,7 +3427,7 @@ "soap", "zf2" ], - "time": "2016-04-21 16:06:27" + "time": "2016-04-21T16:06:27+00:00" }, { "name": "zendframework/zend-stdlib", @@ -3482,7 +3486,7 @@ "stdlib", "zf2" ], - "time": "2016-04-12 21:17:31" + "time": "2016-04-12T21:17:31+00:00" }, { "name": "zendframework/zend-text", @@ -3529,7 +3533,7 @@ "text", "zf2" ], - "time": "2016-02-08 19:03:52" + "time": "2016-02-08T19:03:52+00:00" }, { "name": "zendframework/zend-uri", @@ -3576,20 +3580,20 @@ "uri", "zf2" ], - "time": "2016-02-17 22:38:51" + "time": "2016-02-17T22:38:51+00:00" }, { "name": "zendframework/zend-validator", - "version": "2.9.0", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", - "reference": "b71641582297eab52753b72cd4eb45a5ded4485c" + "reference": "c9a8160a0191e34bb98ac1ecd4e453391c424bb3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/b71641582297eab52753b72cd4eb45a5ded4485c", - "reference": "b71641582297eab52753b72cd4eb45a5ded4485c", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/c9a8160a0191e34bb98ac1ecd4e453391c424bb3", + "reference": "c9a8160a0191e34bb98ac1ecd4e453391c424bb3", "shasum": "" }, "require": { @@ -3647,7 +3651,7 @@ "validator", "zf2" ], - "time": "2017-03-17 10:15:50" + "time": "2017-05-17T22:06:13+00:00" }, { "name": "zendframework/zend-view", @@ -3734,7 +3738,7 @@ "view", "zf2" ], - "time": "2017-03-21 15:05:56" + "time": "2017-03-21T15:05:56+00:00" } ], "packages-dev": [ @@ -3790,7 +3794,7 @@ "constructor", "instantiate" ], - "time": "2015-06-14 21:17:01" + "time": "2015-06-14T21:17:01+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -3860,7 +3864,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-03-31 12:59:38" + "time": "2017-03-31T12:59:38+00:00" }, { "name": "ircmaxell/password-compat", @@ -3902,7 +3906,7 @@ "hashing", "password" ], - "time": "2014-11-20 16:49:30" + "time": "2014-11-20T16:49:30+00:00" }, { "name": "lusitanian/oauth", @@ -3969,7 +3973,7 @@ "oauth", "security" ], - "time": "2016-07-12 22:15:40" + "time": "2016-07-12T22:15:40+00:00" }, { "name": "pdepend/pdepend", @@ -4009,7 +4013,7 @@ "BSD-3-Clause" ], "description": "Official version of pdepend to be handled with Composer", - "time": "2017-01-19 14:23:36" + "time": "2017-01-19T14:23:36+00:00" }, { "name": "phpmd/phpmd", @@ -4075,7 +4079,7 @@ "phpmd", "pmd" ], - "time": "2017-01-20 14:41:10" + "time": "2017-01-20T14:41:10+00:00" }, { "name": "phpunit/php-code-coverage", @@ -4137,7 +4141,7 @@ "testing", "xunit" ], - "time": "2015-10-06 15:47:00" + "time": "2015-10-06T15:47:00+00:00" }, { "name": "phpunit/php-file-iterator", @@ -4182,7 +4186,7 @@ "filesystem", "iterator" ], - "time": "2013-10-10 15:34:57" + "time": "2013-10-10T15:34:57+00:00" }, { "name": "phpunit/php-text-template", @@ -4223,7 +4227,7 @@ "keywords": [ "template" ], - "time": "2015-06-21 13:50:34" + "time": "2015-06-21T13:50:34+00:00" }, { "name": "phpunit/php-timer", @@ -4272,7 +4276,7 @@ "keywords": [ "timer" ], - "time": "2017-02-26 11:10:40" + "time": "2017-02-26T11:10:40+00:00" }, { "name": "phpunit/php-token-stream", @@ -4321,7 +4325,7 @@ "keywords": [ "tokenizer" ], - "time": "2017-02-27 10:12:30" + "time": "2017-02-27T10:12:30+00:00" }, { "name": "phpunit/phpunit", @@ -4395,7 +4399,7 @@ "testing", "xunit" ], - "time": "2014-05-02 07:13:40" + "time": "2014-05-02T07:13:40+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -4451,7 +4455,7 @@ "mock", "xunit" ], - "time": "2015-10-02 06:51:40" + "time": "2015-10-02T06:51:40+00:00" }, { "name": "sebastian/comparator", @@ -4515,27 +4519,27 @@ "compare", "equality" ], - "time": "2017-01-29 09:50:25" + "time": "2017-01-29T09:50:25+00:00" }, { "name": "sebastian/diff", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^5.3.3 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "~4.8" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" }, "type": "library", "extra": { @@ -4567,7 +4571,7 @@ "keywords": [ "diff" ], - "time": "2015-12-08 07:14:41" + "time": "2017-05-22T07:24:03+00:00" }, { "name": "sebastian/environment", @@ -4617,7 +4621,7 @@ "environment", "hhvm" ], - "time": "2016-08-18 05:49:44" + "time": "2016-08-18T05:49:44+00:00" }, { "name": "sebastian/exporter", @@ -4684,7 +4688,7 @@ "export", "exporter" ], - "time": "2016-06-17 09:04:28" + "time": "2016-06-17T09:04:28+00:00" }, { "name": "sebastian/finder-facade", @@ -4723,7 +4727,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", - "time": "2016-02-17 07:02:23" + "time": "2016-02-17T07:02:23+00:00" }, { "name": "sebastian/phpcpd", @@ -4774,7 +4778,7 @@ ], "description": "Copy/Paste Detector (CPD) for PHP code.", "homepage": "https://github.com/sebastianbergmann/phpcpd", - "time": "2016-04-17 19:32:49" + "time": "2016-04-17T19:32:49+00:00" }, { "name": "sebastian/recursion-context", @@ -4827,7 +4831,7 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2016-10-03 07:41:43" + "time": "2016-10-03T07:41:43+00:00" }, { "name": "sebastian/version", @@ -4862,7 +4866,7 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", - "time": "2015-06-21 13:59:46" + "time": "2015-06-21T13:59:46+00:00" }, { "name": "squizlabs/php_codesniffer", @@ -4937,11 +4941,11 @@ "phpcs", "standards" ], - "time": "2014-05-01 03:07:07" + "time": "2014-05-01T03:07:07+00:00" }, { "name": "symfony/config", - "version": "v3.2.8", + "version": "v3.2.9", "source": { "type": "git", "url": "https://github.com/symfony/config.git", @@ -4993,7 +4997,7 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2017-04-12 14:13:17" + "time": "2017-04-12T14:13:17+00:00" }, { "name": "symfony/dependency-injection", @@ -5053,20 +5057,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2017-01-28 00:04:57" + "time": "2017-01-28T00:04:57+00:00" }, { "name": "symfony/polyfill-php54", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0" + "reference": "7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/90e085822963fdcc9d1c5b73deb3d2e5783b16a0", - "reference": "90e085822963fdcc9d1c5b73deb3d2e5783b16a0", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789", + "reference": "7dd1a8b9f0442273fdfeb1c4f5eaff6890a82789", "shasum": "" }, "require": { @@ -5075,7 +5079,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -5111,20 +5115,20 @@ "portable", "shim" ], - "time": "2016-11-14 01:06:16" + "time": "2017-06-09T08:25:21+00:00" }, { "name": "symfony/polyfill-php55", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "03e3f0350bca2220e3623a0e340eef194405fc67" + "reference": "94566239a7720cde0820f15f0cc348ddb51ba51d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/03e3f0350bca2220e3623a0e340eef194405fc67", - "reference": "03e3f0350bca2220e3623a0e340eef194405fc67", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/94566239a7720cde0820f15f0cc348ddb51ba51d", + "reference": "94566239a7720cde0820f15f0cc348ddb51ba51d", "shasum": "" }, "require": { @@ -5134,7 +5138,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -5167,20 +5171,20 @@ "portable", "shim" ], - "time": "2016-11-14 01:06:16" + "time": "2017-06-09T08:25:21+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.3.0", + "version": "v1.4.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "13ce343935f0f91ca89605a2f6ca6f5c2f3faac2" + "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/13ce343935f0f91ca89605a2f6ca6f5c2f3faac2", - "reference": "13ce343935f0f91ca89605a2f6ca6f5c2f3faac2", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/032fd647d5c11a9ceab8ee8747e13b5448e93874", + "reference": "032fd647d5c11a9ceab8ee8747e13b5448e93874", "shasum": "" }, "require": { @@ -5190,7 +5194,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { @@ -5226,37 +5230,34 @@ "portable", "shim" ], - "time": "2016-11-14 01:06:16" + "time": "2017-06-09T14:24:12+00:00" }, { - "name": "symfony/polyfill-xml", - "version": "v1.3.0", + "name": "symfony/polyfill-php72", + "version": "v1.4.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-xml.git", - "reference": "64b6a864f18ab4fddad49f5025f805f6781dfabd" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-xml/zipball/64b6a864f18ab4fddad49f5025f805f6781dfabd", - "reference": "64b6a864f18ab4fddad49f5025f805f6781dfabd", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", + "reference": "d3a71580c1e2cab33b6d705f0ec40e9015e14d5c", "shasum": "" }, "require": { "php": ">=5.3.3" }, - "suggest": { - "ext-xml": "For best performance" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3-dev" + "dev-master": "1.4-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Xml\\": "" + "Symfony\\Polyfill\\Php72\\": "" }, "files": [ "bootstrap.php" @@ -5276,6 +5277,54 @@ "homepage": "https://symfony.com/contributors" } ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-09T08:25:21+00:00" + }, + { + "name": "symfony/polyfill-xml", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-xml.git", + "reference": "89326af9d173053826ae8fe26a6f49597ba4e9f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-xml/zipball/89326af9d173053826ae8fe26a6f49597ba4e9f3", + "reference": "89326af9d173053826ae8fe26a6f49597ba4e9f3", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-php72": "~1.4" + }, + "type": "metapackage", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], "description": "Symfony polyfill for xml's utf8_encode and utf8_decode functions", "homepage": "https://symfony.com", "keywords": [ @@ -5284,20 +5333,20 @@ "portable", "shim" ], - "time": "2016-11-14 01:06:16" + "time": "2017-06-09T08:25:21+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.2.8", + "version": "v3.3.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3" + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a0105afb670dbd38f521105c444de1b8e10cfe3", - "reference": "5a0105afb670dbd38f521105c444de1b8e10cfe3", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/602a15299dc01556013b07167d4f5d3a60e90d15", + "reference": "602a15299dc01556013b07167d4f5d3a60e90d15", "shasum": "" }, "require": { @@ -5306,7 +5355,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -5333,20 +5382,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2017-04-12 14:13:17" + "time": "2017-04-12T14:14:56+00:00" }, { "name": "symfony/yaml", - "version": "v2.8.20", + "version": "v2.8.22", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "93ccdde79f4b079c7558da4656a3cb1c50c68e02" + "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/93ccdde79f4b079c7558da4656a3cb1c50c68e02", - "reference": "93ccdde79f4b079c7558da4656a3cb1c50c68e02", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5", + "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5", "shasum": "" }, "require": { @@ -5382,7 +5431,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-05-01 14:31:55" + "time": "2017-06-01T20:52:29+00:00" }, { "name": "theseer/fdomdocument", @@ -5422,7 +5471,7 @@ ], "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.", "homepage": "https://github.com/theseer/fDOMDocument", - "time": "2017-04-21 14:50:31" + "time": "2017-04-21T14:50:31+00:00" } ], "aliases": [], @@ -5449,7 +5498,8 @@ "ext-mbstring": "*", "ext-openssl": "*", "ext-zip": "*", - "ext-pdo_mysql": "*" + "ext-pdo_mysql": "*", + "ext-soap": "*" }, "platform-dev": [] } diff --git a/dev/tests/api-functional/testsuite/Magento/Bundle/Api/OrderInvoiceCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/OrderInvoiceCreateTest.php new file mode 100644 index 000000000000..3a40e510326a --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/Bundle/Api/OrderInvoiceCreateTest.php @@ -0,0 +1,143 @@ +objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->invoiceRepository = $this->objectManager->get( + \Magento\Sales\Api\InvoiceRepositoryInterface::class + ); + } + + /** + * Test create a partial invoice for order with bundle and Simple products. + * + * @return void + * @magentoApiDataFixture Magento/Bundle/_files/order_items_simple_and_bundle.php + */ + public function testInvoiceWithSimpleAndBundleCreate() + { + /** @var \Magento\Sales\Api\Data\OrderInterface $existingOrder*/ + $existingOrder = $this->objectManager->create(\Magento\Sales\Api\Data\OrderInterface::class) + ->loadByIncrementId('100000001'); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/order/' . $existingOrder->getId() . '/invoice', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_READ_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_READ_NAME . 'execute', + ], + ]; + + $requestData = [ + 'orderId' => $existingOrder->getId(), + 'items' => [], + 'comment' => [ + 'comment' => 'Test Comment', + 'is_visible_on_front' => 1, + ], + ]; + $grantTotal = 0; + foreach ($existingOrder->getAllItems() as $item) { + $requestData['items'] = []; + $requestData['items'][] = [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ]; + $result = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotEmpty($result); + try { + $invoice = $this->invoiceRepository->get($result); + $grantTotal += $invoice->getGrandTotal(); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + $this->fail('Failed asserting that Invoice was created'); + } + } + $this->assertNotEquals( + $existingOrder->getGrandTotal(), + $grantTotal, + 'Failed asserting that invoice is correct.' + ); + } + + /** + * Test create invoice with Bundle product. + * + * @return void + * @magentoApiDataFixture Magento/Bundle/_files/order_item_with_bundle_and_options.php + */ + public function testInvoiceWithBundleCreate() + { + /** @var \Magento\Sales\Api\Data\OrderInterface $existingOrder*/ + $existingOrder = $this->objectManager->create(\Magento\Sales\Api\Data\OrderInterface::class) + ->loadByIncrementId('100000001'); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/order/' . $existingOrder->getId() . '/invoice', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_READ_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_READ_NAME . 'execute', + ], + ]; + + $requestData = [ + 'orderId' => $existingOrder->getId(), + 'items' => [], + 'comment' => [ + 'comment' => 'Test Comment', + 'is_visible_on_front' => 1, + ], + ]; + + /** @var \Magento\Sales\Api\Data\OrderItemInterface $item */ + foreach ($existingOrder->getAllItems() as $item) { + $requestData['items'][] = [ + 'order_item_id' => $item->getItemId(), + 'qty' => $item->getQtyOrdered(), + ]; + } + $result = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotEmpty($result); + $invoice = $this->invoiceRepository->get($result); + $this->assertNotEquals( + $existingOrder->getGrandTotal(), + $invoice->getGrandTotal(), + 'Failed asserting that invoice is correct.' + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Cms/Api/BlockRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Cms/Api/BlockRepositoryTest.php index 15ccd5e2586d..3c00fdc1ae86 100644 --- a/dev/tests/api-functional/testsuite/Magento/Cms/Api/BlockRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Cms/Api/BlockRepositoryTest.php @@ -262,6 +262,7 @@ public function testSearch() $searchData = $searchCriteriaBuilder->create()->__toArray(); $requestData = ['searchCriteria' => $searchData]; + $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . "/search" . '?' . http_build_query($requestData), diff --git a/dev/tests/api-functional/testsuite/Magento/InventoryApi/Api/SourceRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/InventoryApi/Api/SourceRepositoryTest.php new file mode 100644 index 000000000000..ee56edfba18f --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/InventoryApi/Api/SourceRepositoryTest.php @@ -0,0 +1,422 @@ +sourceFactory = Bootstrap::getObjectManager() + ->create(SourceInterfaceFactory::class); + + $this->countryInformationAcquirer = Bootstrap::getObjectManager() + ->create(CountryInformationAcquirerInterface::class); + + $this->sourceCarrierLinkFactory = Bootstrap::getObjectManager() + ->create(SourceCarrierLinkInterfaceFactory::class); + + $this->sourceRepository = Bootstrap::getObjectManager() + ->create(SourceRepositoryInterface::class); + + $this->searchCriteriaBuilder = Bootstrap::getObjectManager() + ->create(SearchCriteriaBuilder::class); + + $this->filterBuilder = Bootstrap::getObjectManager() + ->create(FilterBuilder::class); + + $this->sortOrderBuilder = Bootstrap::getObjectManager() + ->create(SortOrderBuilder::class); + } + + /** + * @param array $sourceData + * @return array + */ + private function getExpectedValues(array $sourceData) + { + $sourceData[SourceInterface::LATITUDE] = number_format( + $sourceData[SourceInterface::LATITUDE], + 6 + ); + $sourceData[SourceInterface::LONGITUDE] = number_format( + $sourceData[SourceInterface::LONGITUDE], + 6 + ); + + return $sourceData; + } + + /** + * @param SourceInterface $source + * @return array + */ + private function getSourceDataArray(SourceInterface $source) + { + $result = [ + SourceInterface::NAME => $source->getName(), + SourceInterface::CITY => $source->getCity(), + SourceInterface::POSTCODE => $source->getPostcode(), + SourceInterface::CONTACT_NAME => $source->getContactName(), + SourceInterface::COUNTRY_ID => $source->getCountryId(), + SourceInterface::DESCRIPTION => $source->getDescription(), + SourceInterface::EMAIL => $source->getEmail(), + SourceInterface::STREET => $source->getStreet(), + SourceInterface::FAX => $source->getFax(), + SourceInterface::PHONE => $source->getPhone(), + SourceInterface::REGION => $source->getRegion(), + SourceInterface::REGION_ID => $source->getRegionId(), + SourceInterface::LATITUDE => $source->getLatitude(), + SourceInterface::LONGITUDE => $source->getLongitude(), + SourceInterface::ENABLED => $source->isEnabled(), + SourceInterface::PRIORITY => $source->getPriority(), + SourceInterface::USE_DEFAULT_CARRIER_CONFIG => $source->isUseDefaultCarrierConfig(), + SourceInterface::CARRIER_LINKS => [] + ]; + + $carrierLinks = $source->getCarrierLinks(); + if ($carrierLinks) { + foreach ($carrierLinks as $carrierLink) { + $result[SourceInterface::CARRIER_LINKS][] = [ + SourceCarrierLinkInterface::CARRIER_CODE => $carrierLink->getCarrierCode(), + SourceCarrierLinkInterface::POSITION => $carrierLink->getPosition(), + ]; + } + } + + return $result; + } + + /** + * @param int $countCarrier + * @param string $postcode + * @return SourceInterface + */ + private function createRandomSource($countCarrier = 2, $postcode = '54321', $enabled = true) + { + $country = $this->countryInformationAcquirer->getCountryInfo('US'); + $regions = $country->getAvailableRegions(); + $region = $regions[mt_rand(0, count($regions) - 1)]; + + $name = uniqid(self::TEST_PREFIX, false); + $description = 'This is an inventory source created by api-functional tests'; + $city = 'Exampletown'; + $street = 'Some Street 455'; + $contactName = 'Contact Name'; + $email = 'example.guy@test.com'; + $fax = '0120002066033'; + $phone = '01660002020044'; + $latitude = 51.343479; + $longitude = 12.387772; + $priority = mt_rand(1, 999); + + $carriers = []; + for ($index = 1; $index <= $countCarrier; $index++) { + $carrierCode = 'CAR-' . $index; + $carrier = $this->sourceCarrierLinkFactory->create(); + $carrier->setPosition($index); + $carrier->setCarrierCode($carrierCode); + $carriers[] = $carrier; + } + + /** @var \Magento\InventoryApi\Api\Data\SourceInterface $source */ + $source = $this->sourceFactory->create(); + $source->setName($name); + $source->setCity($city); + $source->setPostcode($postcode); + $source->setContactName($contactName); + $source->setCountryId($country->getId()); + $source->setDescription($description); + $source->setEmail($email); + $source->setStreet($street); + $source->setFax($fax); + $source->setPhone($phone); + $source->setRegion($region->getName()); + $source->setRegionId($region->getId()); + $source->setLatitude($latitude); + $source->setLongitude($longitude); + $source->setEnabled($enabled); + $source->setPriority($priority); + $source->setCarrierLinks($carriers); + $source->setUseDefaultCarrierConfig(true); + + return $source; + } + + /** + * Update the given source in magento + * + * @param SourceInterface $expectedSource + * + * @return int + */ + private function updateSource($expectedSource) + { + $requestData = [ + 'source' => $this->getSourceDataArray($expectedSource) + ]; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . $expectedSource->getSourceId(), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_PUT, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + // call the webservice to update the source + return $this->_webApiCall($serviceInfo, $requestData); + } + + /** + * Create new Inventory Source using Web API and verify it's integrity. + */ + public function testCreateSource() + { + $expectedSource = $this->createRandomSource(3); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $requestData = [ + 'source' => $this->getSourceDataArray($expectedSource) + ]; + + $result = $this->_webApiCall($serviceInfo, $requestData); + $this->assertNotNull($result); + + $createdSource = $this->sourceRepository->get($result); + $this->assertEquals( + $this->getExpectedValues($this->getSourceDataArray($expectedSource)), + $this->getSourceDataArray($createdSource) + ); + } + + /** + * Load already existing Inventory Source using Web API and verify it's integrity. + */ + public function testGetSource() + { + $expectedSource = $this->createRandomSource(5); + $currentSourceId = $this->sourceRepository->save($expectedSource); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . $currentSourceId, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Get', + ], + ]; + + $result = $this->_webApiCall($serviceInfo, [SourceInterface::SOURCE_ID => $currentSourceId]); + $this->assertNotNull($result); + + $this->assertEquals($currentSourceId, $result[SourceInterface::SOURCE_ID]); + + unset($result[SourceInterface::SOURCE_ID]); + $this->assertEquals($this->getSourceDataArray($expectedSource), $result); + } + + /** + * Load and verify integrity of a list of already existing Inventory Sources filtered by Search Criteria. + */ + public function testGetSourcesList() + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = Bootstrap::getObjectManager() + ->create(SearchCriteriaBuilder::class); + + $postcode1 = uniqid(self::TEST_PREFIX, false); + $postcode2 = uniqid(self::TEST_PREFIX, false); + + $source1 = $this->createRandomSource(2, $postcode1, true); + $this->sourceRepository->save($source1); + + $source2 = $this->createRandomSource(3, $postcode1, false); + $this->sourceRepository->save($source2); + + $source3 = $this->createRandomSource(1, $postcode2, true); + $this->sourceRepository->save($source3); + + $source4 = $this->createRandomSource(3, $postcode2, true); + $this->sourceRepository->save($source4); + + // add filters to find all active items with created postcode + $postcodeFilter = implode(',', [$postcode1, $postcode2]); + $searchCriteriaBuilder->addFilter('postcode', $postcodeFilter, 'in'); + $searchCriteriaBuilder->addFilter('enabled', 1, 'eq'); + + $searchData = $searchCriteriaBuilder->create()->__toArray(); + $requestData = ['searchCriteria' => $searchData]; + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . 'search?' . http_build_query($requestData), + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'GetList', + ], + ]; + + $searchResult = $this->_webApiCall($serviceInfo, $requestData); + + $this->assertEquals(3, count($searchResult['items'])); + $this->assertEquals( + $searchResult['items'][0][SourceInterface::SOURCE_ID], + $source1->getSourceId() + ); + $this->assertEquals( + $searchResult['items'][1][SourceInterface::SOURCE_ID], + $source3->getSourceId() + ); + + /** @var SourceCarrierLinkInterface[] $carrierLinks */ + $resultCarrierLink = $searchResult['items'][0][SourceInterface::CARRIER_LINKS]; + + $counter = 0; + foreach ($source1->getCarrierLinks() as $carrierLink) { + $carrierCode = $resultCarrierLink[$counter][SourceCarrierLinkInterface::CARRIER_CODE]; + $this->assertEquals($carrierLink->getCarrierCode(), $carrierCode); + $counter++; + } + } + + /** + * Update already existing Inventory Source using Web API and verify it's integrity. + */ + public function testUpdateSource() + { + // create a new source + $expectedSource = $this->createRandomSource(2); + $this->sourceRepository->save($expectedSource); + + // set name and city property's in the source to update them + $expectedName = uniqid('UpdatedName_', false); + $expectedCity = uniqid('UpdatedCity_', false); + $expectedSource->setName($expectedName); + $expectedSource->setCity($expectedCity); + $updateSourceId = $this->updateSource($expectedSource); + + // verify it's integrity + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . $updateSourceId, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Get', + ], + ]; + + $result = $this->_webApiCall($serviceInfo, [SourceInterface::SOURCE_ID => $updateSourceId]); + $this->assertEquals($expectedName, $result[SourceInterface::NAME]); + $this->assertEquals($expectedCity, $result[SourceInterface::CITY]); + } + + /** + * Update already existing Inventory Source, removing carrier links, using Web API and verify it's integrity. + */ + public function testUpdateSourceWithoutCarriers() + { + $expectedSource = $this->createRandomSource(2); + $this->sourceRepository->save($expectedSource); + + $carriers = []; + $expectedSource->setCarrierLinks($carriers); + $updateSourceId = $this->updateSource($expectedSource); + + // verify it's integrity + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . $updateSourceId, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Get', + ], + ]; + + $result = $this->_webApiCall($serviceInfo, [SourceInterface::SOURCE_ID => $updateSourceId]); + $this->assertEquals($carriers, $result[SourceInterface::CARRIER_LINKS]); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertBIEssentialsLink.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertBIEssentialsLink.php index be2e5a9447a4..010d9c446819 100644 --- a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertBIEssentialsLink.php +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertBIEssentialsLink.php @@ -3,16 +3,23 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Analytics\Test\Constraint; use Magento\Mtf\Client\BrowserInterface; use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Backend\Test\Page\Adminhtml\Dashboard; /** * Assert BI Essentials Sign Up page is opened by admin menu link */ class AssertBIEssentialsLink extends AbstractConstraint { + /** + * Count of try for choose menu item. + */ + const MAX_TRY_COUNT = 2; + /** * Browser instance. * @@ -25,19 +32,46 @@ class AssertBIEssentialsLink extends AbstractConstraint * * @param BrowserInterface $browser * @param string $businessIntelligenceLink + * @param Dashboard $dashboard + * @param string $menuItem + * @param bool $waitMenuItemNotVisible * @return void */ - public function processAssert(BrowserInterface $browser, $businessIntelligenceLink) - { + public function processAssert( + BrowserInterface $browser, + $businessIntelligenceLink, + Dashboard $dashboard, + $menuItem, + $waitMenuItemNotVisible = false + ) { + /** + * In the parallel run case new windows that adding to selenium grid windows handler + * are in competition with another windows in another browsers in the same selenium grid. + * During this case need to have some algorithm for retrying some operations that changed + * current window for browser, because it's some times happens. + */ $this->browser = $browser; - $this->browser->selectWindow(); - \PHPUnit_Framework_Assert::assertTrue( - $this->browser->waitUntil( - function () use ($businessIntelligenceLink) { + $count = 0; + $isVisible = false; + do { + try { + $this->browser->selectWindow(); + $isVisible = $this->browser->waitUntil(function () use ($businessIntelligenceLink) { return ($this->browser->getUrl() === $businessIntelligenceLink) ?: null; - } - ), - 'BI Essentials Sign Up page was not opened by link.' + }); + break; + } catch (\Throwable $e) { + $dashboard->open(); + $dashboard->getMenuBlock()->navigate($menuItem, $waitMenuItemNotVisible); + $count++; + } + } while ($count < self::MAX_TRY_COUNT); + + \PHPUnit_Framework_Assert::assertTrue( + $isVisible, + "BI Essentials Sign Up page was not opened by link.\n + Actual link is '{$this->browser->getUrl()}'\n + Expected link is '$businessIntelligenceLink'" ); } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/AdvancedPricing.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/AdvancedPricing.php index 7aeb5c1e238d..bc64f6ab6f23 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/AdvancedPricing.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/AdvancedPricing.php @@ -10,6 +10,7 @@ use Magento\Mtf\Client\Element\SimpleElement; use Magento\Ui\Test\Block\Adminhtml\Section; use Magento\Catalog\Test\Block\Adminhtml\Product\Edit\Section\Options\AbstractOptions; +use Magento\Mtf\Client\Locator; /** * Product advanced pricing section. @@ -37,6 +38,13 @@ class AdvancedPricing extends Section */ protected $doneButton = '.action-primary[data-role="action"]'; + /** + * Selector for field. + * + * @var string + */ + private $fieldByName = '//*[contains(text(),"%s")]/preceding::div[2]/ancestor::div[1]'; + /** * Fill 'Advanced price' product form on tab. * @@ -104,4 +112,15 @@ public function getTierPriceForm(SimpleElement $element = null) ['element' => $element] ); } + + /** + * Check if the field is displayed correctly. + * + * @param string $fieldName + * @return bool + */ + public function checkField($fieldName) + { + return $this->_rootElement->find(sprintf($this->fieldByName, $fieldName), Locator::SELECTOR_XPATH)->isVisible(); + } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/CategoryIds.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/CategoryIds.php index 45c31f998ca3..2dc6a9c17dd2 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/CategoryIds.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/ProductDetails/CategoryIds.php @@ -46,13 +46,6 @@ class CategoryIds extends MultisuggestElement */ protected $advancedInventoryButton = '[data-index="advanced_inventory_button"]'; - /** - * Locator for MultiSelect element. - * - * @var string - */ - private $multiSelectElement = '.admin__action-multiselect-menu-inner-item'; - /** * @constructor * @param BrowserInterface $browser @@ -93,14 +86,15 @@ public function setValue($values) if ($value == '') { continue; } - $this->keys([$value]); - - // wait when some element of multiSelect will be visible. - $this->waitUntil(function () { - return $this->find($this->multiSelectElement)->isVisible() ? true : null; - }); + $this->keys([$value]); $searchedItem = $this->find(sprintf($this->resultItem, $value), Locator::SELECTOR_XPATH); + $searchedCountElements = $this->find($this->searchedCount); + $this->waitUntil( + function () use ($searchedCountElements) { + return $searchedCountElements->isVisible() ? true : null; + } + ); $searchedItem->click(); $closeButton = $this->find($this->closeButton); diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.php index e6a7f60bcec0..ebd455fa1ed9 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.php @@ -28,6 +28,7 @@ class ClearAllCompareProductsTest extends AbstractCompareProductsTest { /* tags */ const MVP = 'yes'; + const TEST_TYPE = 'extended_acceptance_test'; /* end tags */ /** diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.xml index 1a702147b40f..523156fb0de7 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ClearAllCompareProductsTest.xml @@ -8,7 +8,6 @@ - stable:no compare_products catalogProductSimple::simple_for_composite_products,catalogProductVirtual::default,downloadableProduct::default,groupedProduct::grouped_product_with_price,configurableProduct::default,bundleProduct::bundle_dynamic_product,bundleProduct::bundle_fixed_product diff --git a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml b/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml deleted file mode 100644 index e01f61dd4780..000000000000 --- a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - option_key_1_%isolation% - 560 - Yes - - - option_key_2_%isolation% - 560 - Yes - - - option_key_3_%isolation% - 560 - Yes - - - - - - catalogProductAttribute::sizes_S_M_L - - - catalogProductSimple::out_of_stock - catalogProductSimple::default - catalogProductSimple::default - - - - - diff --git a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml deleted file mode 100644 index d02d2fdbc020..000000000000 --- a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - configurable-product-%isolation% - three_new_options_with_out_of_stock_product - Configurable Product %isolation% - configurable_sku_%isolation% - 1 - 2 - default_subcategory - Configurable short description - Configurable Product description %isolation% - SIZE_S - - - - - diff --git a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/etc/di.xml deleted file mode 100644 index 73252293f302..000000000000 --- a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/etc/di.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - middle - - - diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Shipping.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Shipping.php index 9f0c6cb6f3a2..10299486a08c 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Shipping.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart/Shipping.php @@ -37,7 +37,7 @@ class Shipping extends Form protected $shippingMethod = '//span[text()="%s"]/following::label[contains(., "%s")]/../input'; /** - * From with shipping available shipping methods. + * Form with shipping available shipping methods. * * @var string */ diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Attribute.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Attribute.php index a515a849bb83..581a997cfab4 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Attribute.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Attribute.php @@ -174,6 +174,13 @@ class Attribute extends Form */ private $attributesGridSpinner = '.productFormConfigurable [data-role="spinner"]'; + /** + * CSS Selector for attribute grid. + * + * @var string + */ + private $attributesGridSelector = '#variation-steps-wizard_step1 .admin__data-grid-outer-wrap'; + /** * Fill attributes * @@ -192,6 +199,7 @@ public function fillAttributes(array $attributes, ConfigurableAttributesData $at } //select attributes + $this->waitAttributesGridLoad(); $this->getAttributesGrid()->resetFilter(); $this->getAttributesGrid()->waitForElementNotVisible($this->attributesGridSpinner); $this->getTemplateBlock()->waitLoader(); @@ -220,6 +228,17 @@ public function fillAttributes(array $attributes, ConfigurableAttributesData $at $this->browser->find($this->nextButton)->click(); } + /** + * Wait for 'Attributes Grid' loaded. + * + * @return void + */ + private function waitAttributesGridLoad() + { + $this->waitForElementVisible($this->attributesGridSelector); + $this->waitForElementNotVisible($this->attributesGridSpinner); + } + /** * @return \Magento\ConfigurableProduct\Test\Block\Adminhtml\Product\AttributesGrid */ @@ -227,7 +246,7 @@ public function getAttributesGrid() { return $this->blockFactory->create( \Magento\ConfigurableProduct\Test\Block\Adminhtml\Product\AttributesGrid::class, - ['element' => $this->browser->find('#variation-steps-wizard_step1 .admin__data-grid-outer-wrap')] + ['element' => $this->browser->find($this->attributesGridSelector)] ); } diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductOutOfStockPage.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductOutOfStockPage.php new file mode 100644 index 000000000000..cf1b2321a554 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductOutOfStockPage.php @@ -0,0 +1,73 @@ +productView->getPriceBlock(); + if (!$priceBlock->isVisible()) { + return "Price block for '{$this->product->getName()}' product' is not visible."; + } + $formPrice = $priceBlock->isOldPriceVisible() ? $priceBlock->getOldPrice() : $priceBlock->getPrice(); + $fixturePrice = $this->getLowestConfigurablePrice(); + + if ($fixturePrice != $formPrice) { + return "Displayed product price on product page (front-end) not equals passed from fixture. " + . "Actual: {$formPrice}, expected: {$fixturePrice}."; + } + return null; + } + + /** + * Returns lowest possible price of configurable product. + * + * @return string + */ + protected function getLowestConfigurablePrice() + { + $price = null; + $priceDataConfig = $this->product->getDataFieldConfig('price'); + if (isset($priceDataConfig['source'])) { + $priceData = $priceDataConfig['source']->getPriceData(); + if (isset($priceData['price_from'])) { + $price = $priceData['price_from']; + } + } + + if (null === $price) { + $configurableOptions = $this->product->getConfigurableAttributesData(); + foreach ($configurableOptions['matrix'] as $option) { + $price = $price === null ? $option['price'] : $price; + if ($price > $option['price']) { + $price = $option['price']; + } + } + } + return $price; + } +} diff --git a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/Constraint/AssertOutOfStockOptionIsAbsentOnProductPage.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertOutOfStockOptionIsAbsentOnProductPage.php similarity index 96% rename from dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/Constraint/AssertOutOfStockOptionIsAbsentOnProductPage.php rename to dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertOutOfStockOptionIsAbsentOnProductPage.php index 1593ced38696..1679d06218e0 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogInventoryConfigurableProduct/Test/Constraint/AssertOutOfStockOptionIsAbsentOnProductPage.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertOutOfStockOptionIsAbsentOnProductPage.php @@ -4,7 +4,7 @@ * See COPYING.txt for license details. */ -namespace Magento\CatalogInventoryConfigurableProduct\Test\Constraint; +namespace Magento\ConfigurableProduct\Test\Constraint; use Magento\Catalog\Test\Page\Product\CatalogProductView; use Magento\Mtf\Client\BrowserInterface; diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml index 14d8dc35a4b2..f1c40933f3b3 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/ConfigurableAttributesData.xml @@ -1109,6 +1109,38 @@ + + + + + + option_key_1_%isolation% + 560 + Yes + + + option_key_2_%isolation% + 560 + Yes + + + option_key_3_%isolation% + 560 + Yes + + + + + + catalogProductAttribute::sizes_S_M_L + + + catalogProductSimple::out_of_stock + catalogProductSimple::default + catalogProductSimple::default + + + diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml index 6cc4d5bbdbba..8bf30474d252 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml @@ -170,7 +170,7 @@ - test_type:acceptance_test, test_type:extended_acceptance_test, stable:no + test_type:acceptance_test, test_type:extended_acceptance_test with_out_of_stock_item Configurable Product %isolation% configurable_sku_%isolation% @@ -186,9 +186,10 @@ + - test_type:acceptance_test, test_type:extended_acceptance_test, stable:no + test_type:acceptance_test, test_type:extended_acceptance_test with_disabled_item Configurable Product %isolation% configurable_sku_%isolation% @@ -206,7 +207,7 @@ - test_type:acceptance_test, test_type:extended_acceptance_test, stable:no + test_type:acceptance_test, test_type:extended_acceptance_test with_one_disabled_item_and_one_out_of_stock_item Configurable Product %isolation% configurable_sku_%isolation% @@ -236,5 +237,19 @@ + + configurable-product-%isolation% + three_new_options_with_out_of_stock_product + Configurable Product %isolation% + configurable_sku_%isolation% + 1 + 2 + default_subcategory + Configurable short description + Configurable Product description %isolation% + SIZE_S + + + diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/etc/di.xml index 6b7d32321d7a..1086169c3213 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/etc/di.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/etc/di.xml @@ -26,6 +26,11 @@ high + + + middle + + diff --git a/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php b/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php new file mode 100644 index 000000000000..a9860ab7b771 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php @@ -0,0 +1,51 @@ +open(['id' => $product->getId()]); + $catalogProductEdit->getProductForm()->openSection('advanced-pricing'); + $advancedPricing = $catalogProductEdit->getProductForm()->getSection('advanced-pricing'); + + \PHPUnit_Framework_Assert::assertTrue( + $advancedPricing->checkField($this->manufacturerFieldTitle), + '"Manufacturer\'s Suggested Retail Price" field is not correct.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return '"Manufacturer\'s Suggested Retail Price" field is correct.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Msrp/Test/TestCase/ApplyMapTest.xml b/dev/tests/functional/tests/app/Magento/Msrp/Test/TestCase/ApplyMapTest.xml index a5efdc3d5e91..32d86dec7a52 100644 --- a/dev/tests/functional/tests/app/Magento/Msrp/Test/TestCase/ApplyMapTest.xml +++ b/dev/tests/functional/tests/app/Magento/Msrp/Test/TestCase/ApplyMapTest.xml @@ -46,5 +46,9 @@ + + bundleProduct::bundle_fixed_product + + diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml index f682cb10f462..10b9d3e80d0c 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml @@ -202,8 +202,8 @@ No Coupon 0 Yes - {Products subselection|total quantity|is|2|:[[Price in cart|is|50]]} - [Price in cart|is|50] + {Products subselection|qty|is|2|:[[Price in cart|is|50]]} + {Conditions combination|ALL|TRUE|:[[Price in cart|is|50]]} Percent of product price discount 10 1 diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml index 3b7a38f9c0b5..e160fef60954 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml @@ -8,7 +8,6 @@ - to_maintain:yes active_sales_rule_product_subselection active_sales_rule_product_attribute 200.00 diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php index d7f7fca3d674..f13f88148e31 100644 --- a/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/ProductTest.php @@ -19,6 +19,7 @@ use Magento\Catalog\Model\Product\Visibility; use Magento\Framework\ObjectManagerInterface; use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Entity; use Magento\TestFramework\Helper\Bootstrap; @@ -112,6 +113,10 @@ public function testMultipleStores() self::assertNotEquals($store->getId(), $bundle->getStoreId()); + /** @var StoreManagerInterface $storeManager */ + $storeManager = $this->objectManager->get(StoreManagerInterface::class); + $storeManager->setCurrentStore($store->getId()); + $bundle->setStoreId($store->getId()) ->setCopyFromView(true); $updatedBundle = $productRepository->save($bundle); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_items_simple_and_bundle.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_items_simple_and_bundle.php new file mode 100644 index 000000000000..8ca201225f84 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_items_simple_and_bundle.php @@ -0,0 +1,23 @@ +create(\Magento\Sales\Model\Order\Item::class); +/** @var $product \Magento\Catalog\Model\Product */ +$orderItem->setProductId($product->getId())->setQtyOrdered(1); +$orderItem->setBasePrice($product->getPrice()); +$orderItem->setPrice($product->getPrice()); +$orderItem->setRowTotal($product->getPrice()); +$orderItem->setProductType('simple'); + +/** @var \Magento\Sales\Model\Order $order */ +$order->addItem($orderItem); +$order->save(); diff --git a/dev/tests/integration/testsuite/Magento/Bundle/_files/order_items_simple_and_bundle_rollback.php b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_items_simple_and_bundle_rollback.php new file mode 100644 index 000000000000..a3b4dd410913 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/_files/order_items_simple_and_bundle_rollback.php @@ -0,0 +1,9 @@ +get(\Magento\Eav\Model\Config::class) ->getAttribute('catalog_product', 'multiselect_attribute'); - $attr->setIsFilterable(1)->save(); /** @var $options \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection */ $options = $objectManager->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php new file mode 100644 index 000000000000..e4821afcd24d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Ui/DataProvider/Product/Attributes/ListingTest.php @@ -0,0 +1,53 @@ +request = $objectManager->get(\Magento\Framework\App\RequestInterface::class); + + /** Default Attribute Set Id is equal 4 */ + $this->request->setParams(['template_id' => 4]); + + $this->dataProvider = $objectManager->create( + \Magento\Catalog\Ui\DataProvider\Product\Attributes\Listing::class, + [ + 'name' => 'product_attributes_grid_data_source', + 'primaryFieldName' => 'attribute_id', + 'requestFieldName' => 'id', + 'request' => $this->request + ] + ); + } + + public function testGetDataSortedAsc() + { + $this->dataProvider->addOrder('attribute_code', 'asc'); + $data = $this->dataProvider->getData(); + $this->assertEquals(2, $data['totalRecords']); + $this->assertEquals('color', $data['items'][0]['attribute_code']); + $this->assertEquals('manufacturer', $data['items'][1]['attribute_code']); + } + + public function testGetDataSortedDesc() + { + $this->dataProvider->addOrder('attribute_code', 'desc'); + $data = $this->dataProvider->getData(); + $this->assertEquals(2, $data['totalRecords']); + $this->assertEquals('manufacturer', $data['items'][0]['attribute_code']); + $this->assertEquals('color', $data['items'][1]['attribute_code']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php index ee258f4d209a..6ab607b5de88 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php @@ -24,7 +24,7 @@ 'is_searchable' => 0, 'is_visible_in_advanced_search' => 0, 'is_comparable' => 0, - 'is_filterable' => 0, + 'is_filterable' => 1, 'is_filterable_in_search' => 0, 'is_used_for_promo_rules' => 0, 'is_html_allowed_on_front' => 1, diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php new file mode 100644 index 000000000000..521eff7c89ef --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/ByStockItemRepositoryTest.php @@ -0,0 +1,74 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test stock item saving via stock item repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSave() + { + /** @var ProductInterface $product */ + $product = $this->productRepository->get('simple', false, null, true); + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray( + $stockItem, + $this->stockItemData, + StockItemInterface::class + ); + $this->stockItemRepository->save($stockItem); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php new file mode 100644 index 000000000000..d11e2661c608 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByQuantityAndStockStatusTest.php @@ -0,0 +1,101 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * model (deprecated) + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setQuantityAndStockStatus($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product model (deprecated) + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php new file mode 100644 index 000000000000..a840b48c1853 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockDataTest.php @@ -0,0 +1,100 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product model (deprecated) + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setStockData($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product model (deprecated) + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('stock_data', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php new file mode 100644 index 000000000000..412ddcfd1735 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductModel/ByStockItemTest.php @@ -0,0 +1,112 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->create(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item by product data via product model (deprecated) + */ + public function testSave() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $productData = $this->productData; + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via product + * model (deprecated) + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->getExtensionAttributes()->setStockItem($stockItem); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php new file mode 100644 index 000000000000..d40a78d16d46 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByQuantityAndStockStatusTest.php @@ -0,0 +1,110 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + // prevent internal caching in property + $this->productRepository->cleanCache(); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * repository + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setQuantityAndStockStatus($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product repository + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php new file mode 100644 index 000000000000..271786918209 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockDataTest.php @@ -0,0 +1,109 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + // prevent internal caching in property + $this->productRepository->cleanCache(); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->get(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product repository + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setStockData($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product repository + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->setData('stock_data', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php new file mode 100644 index 000000000000..daf333a512f3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductCreate/ByProductRepository/ByStockItemTest.php @@ -0,0 +1,144 @@ + Type::TYPE_SIMPLE, + 'website_ids' => [1], + ProductInterface::NAME => 'Simple', + ProductInterface::SKU => 'simple', + ProductInterface::PRICE => 100, + ProductInterface::EXTENSION_ATTRIBUTES_KEY => [], + ]; + + /** + * @var array + */ + private $stockItemData = [ + StockItemInterface::QTY => 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productFactory = $objectManager->get(ProductInterfaceFactory::class); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + // prevent internal caching in property + $this->productRepository->cleanCache(); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + + /** @var CategorySetup $installer */ + $installer = $objectManager->create(CategorySetup::class); + $attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); + $this->productData[ProductInterface::ATTRIBUTE_SET_ID] = $attributeSetId; + } + + /** + * Test saving of stock item by product data via product repository + */ + public function testSave() + { + /** @var ProductInterface $product */ + $product = $this->productFactory->create(); + $productData = $this->productData; + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via product + * repository + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var ProductInterface $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product->getExtensionAttributes()->setStockItem($stockItem); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test automatically stock item creating on product save via product repository + */ + public function testAutomaticallyStockItemCreating() + { + /** @var ProductInterface $product */ + $product = $this->productFactory->create(); + $this->dataObjectHelper->populateWithArray($product, $this->productData, ProductInterface::class); + $product = $this->productRepository->save($product); + + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + $this->stockItemRepository->save($stockItem); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php new file mode 100644 index 000000000000..bc5c623e4999 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByQuantityAndStockStatusTest.php @@ -0,0 +1,73 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setQuantityAndStockStatus($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php new file mode 100644 index 000000000000..c2468b690f95 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockDataTest.php @@ -0,0 +1,73 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product + * model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setStockData($this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('stock_data', $this->stockItemData); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php new file mode 100644 index 000000000000..59c6d7a4554a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductModel/ByStockItemTest.php @@ -0,0 +1,113 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item by product data via product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSave() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via + * product model (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->getExtensionAttributes()->setStockItem($stockItem); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually updated stock item (obtained from extension attributes object) on product save via + * product repository (deprecated) + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyUpdatedStockItem() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray( + $stockItem, + $this->stockItemData, + StockItemInterface::class + ); + $product->save(); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php new file mode 100644 index 000000000000..0bfe40250505 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByQuantityAndStockStatusTest.php @@ -0,0 +1,73 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setQuantityAndStockStatus' method (deprecated) via product + * repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetQuantityAndStockStatus() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setQuantityAndStockStatus($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'quantity_and_stock_status' key (deprecated) + * via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('quantity_and_stock_status', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php new file mode 100644 index 000000000000..8ff4b89d2480 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockDataTest.php @@ -0,0 +1,72 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item on product save by 'setStockData' method (deprecated) via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetStockData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setStockData($this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of stock item on product save by 'setData' method with 'stock_data' key (deprecated) + * via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveBySetData() + { + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->setData('stock_data', $this->stockItemData); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php new file mode 100644 index 000000000000..8a3786f83990 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/OnProductUpdate/ByProductRepository/ByStockItemTest.php @@ -0,0 +1,120 @@ + 555, + StockItemInterface::MANAGE_STOCK => true, + StockItemInterface::IS_IN_STOCK => false, + ]; + + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->stockItemFactory = $objectManager->get(StockItemInterfaceFactory::class); + $this->productRepository = $objectManager->get(ProductRepositoryInterface::class); + $this->stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class); + $this->dataObjectHelper = $objectManager->get(DataObjectHelper::class); + $this->stockItemDataChecker = $objectManager->get(StockItemDataChecker::class); + } + + /** + * Test saving of stock item by product data via product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSave() + { + /** @var ProductInterface $product */ + $product = $this->productRepository->get('simple', false, null, true); + $productData[ProductInterface::EXTENSION_ATTRIBUTES_KEY]['stock_item'] = $this->stockItemData; + $this->dataObjectHelper->populateWithArray($product, $productData, ProductInterface::class); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually created stock item (and set by extension attributes object) on product save via + * product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyCreatedStockItem() + { + /** @var StockItemInterface $stockItem */ + $stockItem = $this->stockItemFactory->create(); + $this->dataObjectHelper->populateWithArray($stockItem, $this->stockItemData, StockItemInterface::class); + + /** @var Product $product */ + $product = $this->productRepository->get('simple', false, null, true); + $product->getExtensionAttributes()->setStockItem($stockItem); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } + + /** + * Test saving of manually updated stock item (obtained from extension attributes object) on product save via + * product repository + * + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSaveManuallyUpdatedStockItem() + { + /** @var ProductInterface $product */ + $product = $this->productRepository->get('simple', false, null, true); + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $this->dataObjectHelper->populateWithArray( + $stockItem, + $this->stockItemData, + StockItemInterface::class + ); + $this->productRepository->save($product); + + $this->stockItemDataChecker->checkStockItemData('simple', $this->stockItemData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php new file mode 100644 index 000000000000..189f8767bac6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Model/StockItemSave/StockItemDataChecker.php @@ -0,0 +1,131 @@ +hydrator = $hydrator; + $this->stockItemRepository = $stockItemRepository; + $this->stockItemCriteriaFactory = $stockItemCriteriaFactory; + $this->productRepository = $productRepository; + $this->productFactory = $productFactory; + } + + /** + * @param string $sku + * @param array $expectedData + */ + public function checkStockItemData($sku, array $expectedData) + { + $product = $this->productRepository->get($sku, false, null, true); + $this->doCheckStockItemData($product, $expectedData); + + /** @var Product $product */ + $productLoadedByModel = $this->productFactory->create(); + $productLoadedByModel->load($product->getId()); + $this->doCheckStockItemData($product, $expectedData); + } + + /** + * @param Product $product + * @param array $expectedData + */ + private function doCheckStockItemData(Product $product, array $expectedData) + { + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $stockItem = $this->stockItemRepository->get($stockItem->getItemId()); + + $this->assertArrayContains($expectedData, $this->hydrator->extract($stockItem)); + + $criteria = $this->stockItemCriteriaFactory->create(); + $result = $this->stockItemRepository->getList($criteria); + $items = $result->getItems(); + $stockItem = current($items); + $this->assertArrayContains($expectedData, $this->hydrator->extract($stockItem)); + + $expectedQuantityAndStockStatusData = array_intersect_key($expectedData, [ + StockItemInterface::IS_IN_STOCK => 0, + StockItemInterface::QTY => 0, + ]); + \PHPUnit_Framework_Assert::assertNotNull($product->getQuantityAndStockStatus()); + $this->assertArrayContains($expectedQuantityAndStockStatusData, $product->getQuantityAndStockStatus()); + + \PHPUnit_Framework_Assert::assertNotNull($product->getData('quantity_and_stock_status')); + $this->assertArrayContains($expectedQuantityAndStockStatusData, $product->getData('quantity_and_stock_status')); + } + + /** + * @param array $expected + * @param array $actual + * @return void + */ + private function assertArrayContains(array $expected, array $actual) + { + foreach ($expected as $key => $value) { + \PHPUnit_Framework_Assert::assertArrayHasKey( + $key, + $actual, + "Expected value for key '{$key}' is missed" + ); + if (is_array($value)) { + $this->assertArrayContains($value, $actual[$key]); + } else { + \PHPUnit_Framework_Assert::assertEquals( + $value, + $actual[$key], + "Expected value for key '{$key}' doesn't match" + ); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php index 30bf4d222cdf..a2b97d1136e4 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Model/CategoryUrlRewriteGeneratorTest.php @@ -10,6 +10,7 @@ use Magento\UrlRewrite\Model\OptionProvider; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; /** * @magentoAppArea adminhtml @@ -25,7 +26,7 @@ protected function setUp() } /** - * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories.php + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories_with_products.php * @magentoDbIsolation enabled * @magentoAppIsolation enabled */ @@ -50,11 +51,56 @@ public function testGenerateUrlRewritesWithoutSaveHistory() ]; $this->assertResults($categoryExpectedResult, $actualResults); + + /** @var \Magento\Catalog\Model\ProductRepository $productRepository */ + $productRepository = $this->objectManager->create(\Magento\Catalog\Model\ProductRepository::class); + $product = $productRepository->get('12345'); + $productForTest = $product->getId(); + + $productFilter = [ + UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::ENTITY_ID => [$productForTest] + ]; + $actualResults = $this->getActualResults($productFilter); + $productExpectedResult = [ + [ + 'simple-product-two.html', + 'catalog/product/view/id/' . $productForTest, + 1, + 0 + ], + [ + 'new-url/category-1-1/category-1-1-1/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/5', + 1, + 0 + ], + [ + 'new-url/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/3', + 1, + 0 + ], + [ + 'new-url/category-1-1/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/4', + 1, + 0 + ], + [ + '/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/2', + 1, + 0 + ] + ]; + + $this->assertResults($productExpectedResult, $actualResults); } /** * @magentoDbIsolation enabled - * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories.php + * @magentoDataFixture Magento/CatalogUrlRewrite/_files/categories_with_products.php * @magentoAppIsolation enabled */ public function testGenerateUrlRewritesWithSaveHistory() @@ -86,6 +132,69 @@ public function testGenerateUrlRewritesWithSaveHistory() ]; $this->assertResults($categoryExpectedResult, $actualResults); + + /** @var \Magento\Catalog\Model\ProductRepository $productRepository */ + $productRepository = $this->objectManager->create(\Magento\Catalog\Model\ProductRepository::class); + $product = $productRepository->get('12345'); + $productForTest = $product->getId(); + + $productFilter = [ + UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + UrlRewrite::ENTITY_ID => [$productForTest] + ]; + $actualResults = $this->getActualResults($productFilter); + $productExpectedResult = [ + [ + 'simple-product-two.html', + 'catalog/product/view/id/' . $productForTest, + 1, + 0 + ], + [ + 'new-url/category-1-1/category-1-1-1/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/5', + 1, + 0 + ], + [ + 'category-1/category-1-1/category-1-1-1/simple-product-two.html', + 'new-url/category-1-1/category-1-1-1/simple-product-two.html', + 0, + OptionProvider::PERMANENT + ], + [ + 'new-url/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/3', + 1, + 0 + ], + [ + 'new-url/category-1-1/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/4', + 1, + 0 + ], + [ + '/simple-product-two.html', + 'catalog/product/view/id/' . $productForTest . '/category/2', + 1, + 0 + ], + [ + 'category-1/simple-product-two.html', + 'new-url/simple-product-two.html', + 0, + OptionProvider::PERMANENT + ], + [ + 'category-1/category-1-1/simple-product-two.html', + 'new-url/category-1-1/simple-product-two.html', + 0, + OptionProvider::PERMANENT + ], + ]; + + $this->assertResults($productExpectedResult, $actualResults); } /** @@ -145,6 +254,7 @@ protected function getActualResults(array $filter) */ protected function assertResults($expected, $actual) { + $this->assertEquals(count($expected), count($actual), 'Number of rewrites does not match'); foreach ($expected as $row) { $this->assertContains( $row, diff --git a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php new file mode 100644 index 000000000000..f2454594587f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/ProductListTest.php @@ -0,0 +1,80 @@ +objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->block = $this->objectManager->create( + \Magento\CatalogWidget\Block\Product\ProductsList::class + ); + } + + /** + * Make sure that widget conditions are applied to product collection correctly + * + * 1. Create new multiselect attribute with several options + * 2. Create 2 new products and select at least 2 multiselect options for one of these products + * 3. Create product list widget condition based on the new multiselect attribute + * 4. Set at least 2 options of multiselect attribute to match products for the product list widget + * 5. Load collection for product list widget and make sure that number of loaded products is correct + * + * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php + */ + public function testCreateCollection() + { + // Reindex EAV attributes to enable products filtration by created multiselect attribute + /** @var \Magento\Catalog\Model\Indexer\Product\Eav\Processor $eavIndexerProcessor */ + $eavIndexerProcessor = $this->objectManager->get( + \Magento\Catalog\Model\Indexer\Product\Eav\Processor::class + ); + $eavIndexerProcessor->reindexAll(); + + // Prepare conditions + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + ); + $attribute->load('multiselect_attribute', 'attribute_code'); + $multiselectAttributeOptionIds = []; + foreach ($attribute->getOptions() as $option) { + if ($option->getValue()) { + $multiselectAttributeOptionIds[] = $option->getValue(); + } + } + $encodedConditions = '^[`1`:^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Combine`,' + . '`aggregator`:`all`,`value`:`1`,`new_child`:``^],`1--1`:' + . '^[`type`:`Magento||CatalogWidget||Model||Rule||Condition||Product`,' + . '`attribute`:`multiselect_attribute`,`operator`:`^[^]`,' + . '`value`:[`' . implode(',', $multiselectAttributeOptionIds) . '`]^]^]'; + $this->block->setData('conditions_encoded', $encodedConditions); + + // Load products collection filtered using specified conditions and perform assesrions + $productCollection = $this->block->createCollection(); + $productCollection->load(); + $this->assertEquals( + 1, + $productCollection->count(), + "Product collection was not filtered according to the widget condition." + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/Widget/ConditionsTest.php b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/Widget/ConditionsTest.php index 44dd94d86aa2..f4b52cc4219f 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/Widget/ConditionsTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogWidget/Block/Product/Widget/ConditionsTest.php @@ -17,7 +17,7 @@ class ConditionsTest extends \PHPUnit_Framework_TestCase protected $block; /** - * @var \Magento\Framework\ObjectManagerInt + * @var \Magento\Framework\ObjectManagerInterface */ protected $objectManager; diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index 58d510afbd93..ff5afe939d13 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -64,6 +64,38 @@ public function testLogoutAction() $this->assertRedirect($this->stringContains('customer/account/logoutSuccess')); } + /** + * Test that forgot password email message displays special characters correctly. + * + * @magentoConfigFixture current_store customer/password/limit_password_reset_requests_method 0 + * @magentoConfigFixture current_store customer/password/forgot_email_template customer_password_forgot_email_template + * @magentoConfigFixture current_store customer/password/forgot_email_identity support + * @magentoConfigFixture current_store general/store_information/name Test special' characters + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testForgotPasswordEmailMessageWithSpecialCharacters() + { + $email = 'customer@example.com'; + + $this->getRequest() + ->setPostValue([ + 'email' => $email, + ]); + + $this->dispatch('customer/account/forgotPasswordPost'); + $this->assertRedirect($this->stringContains('customer/account/')); + + /** @var \Magento\TestFramework\Mail\Template\TransportBuilderMock $transportBuilder */ + $transportBuilder = $this->_objectManager->get( + \Magento\TestFramework\Mail\Template\TransportBuilderMock::class + ); + $subject = $transportBuilder->getSentMessage()->getSubject(); + $this->assertContains( + 'Test special\' characters', + $subject + ); + } + /** * @magentoDataFixture Magento/Customer/_files/customer.php */ diff --git a/dev/tests/integration/testsuite/Magento/Deploy/DeployTest.php b/dev/tests/integration/testsuite/Magento/Deploy/DeployTest.php index 9c8f2d9a0e58..b856b4d83148 100644 --- a/dev/tests/integration/testsuite/Magento/Deploy/DeployTest.php +++ b/dev/tests/integration/testsuite/Magento/Deploy/DeployTest.php @@ -82,7 +82,7 @@ class DeployTest extends \PHPUnit_Framework_TestCase Options::EXCLUDE_AREA => ['none'], Options::THEME => ['Magento/zoom1', 'Magento/zoom2', 'Magento/zoom3'], Options::EXCLUDE_THEME => ['none'], - Options::LANGUAGE => ['en_US'], + Options::LANGUAGE => ['en_US', 'fr_FR', 'pl_PL'], Options::EXCLUDE_LANGUAGE => ['none'], Options::JOBS_AMOUNT => 0, Options::SYMLINK_LOCALE => false, @@ -137,6 +137,12 @@ public function testDeploy() $this->assertFileExists($this->staticDir->getAbsolutePath('frontend/Magento/zoom3/default/css/root.css')); $this->assertFileExists($this->staticDir->getAbsolutePath('frontend/Magento/zoom3/default/css/local.css')); + $this->assertFileExistsIsGenerated('requirejs-config.js'); + $this->assertFileExistsIsGenerated('requirejs-map.js'); + $this->assertFileExistsIsGenerated('map.json'); + $this->assertFileExistsIsGenerated('js-translation.json'); + $this->assertFileExistsIsGenerated('result_map.json'); + $actualFileContent = $this->staticDir->readFile('frontend/Magento/zoom3/default/css/root.css'); $this->assertLessPreProcessor($actualFileContent); $this->assertCssUrlFixerPostProcessor($actualFileContent); @@ -148,6 +154,25 @@ public function testDeploy() } } + /** + * Assert file exists in all themes and locales + * + * @param string $fileName + * @return void + */ + private function assertFileExistsIsGenerated($fileName) + { + foreach (['Magento/zoom1', 'Magento/zoom2', 'Magento/zoom3'] as $theme) { + foreach ($this->options[Options::LANGUAGE] as $locale) { + $this->assertFileExists( + $this->staticDir->getAbsolutePath( + 'frontend/' . $theme . '/' . $locale . '/' . $fileName + ) + ); + } + } + } + /** * Assert Less pre-processor * diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/expected/styles.magento.min.css b/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/expected/styles.magento.min.css index 257a413f380f..99c21441f9db 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/expected/styles.magento.min.css +++ b/dev/tests/integration/testsuite/Magento/Framework/View/_files/static/expected/styles.magento.min.css @@ -1 +1 @@ -table>caption{margin-bottom:5px}table thead{background:#676056;color:#f7f3eb}table thead .headings{background:#807a6e}table thead a{color:#f7f3eb;display:block}table thead a label{color:#f7f3eb;cursor:pointer;display:block}table thead a:hover,table thead a:focus{color:#dac7a2;text-decoration:none}table tfoot{background:#f2ebde;color:#676056}table tfoot tr th,table tfoot tr td{text-align:left}table th{background:transparent;border:solid #cac3b4;border-width:0 1px;font-size:14px;padding:6px 10px;text-align:center}table td{border:solid #cac3b4;border-width:0 1px;padding:6px 10px 7px;vertical-align:top}table tbody tr td{background:#fff;color:#676056;padding-top:12px}table tbody tr td:first-child{border-left:0}table tbody tr td:first-child input[type="checkbox"]{margin:0}table tbody tr td:last-child{border-right:0}table tbody tr:last-child th,table tbody tr:last-child td{border-bottom-width:1px}table tbody tr:nth-child(odd) td,table tbody tr:nth-child(odd) th{background-color:#f7f3eb}table tbody.even tr td{background:#fff}table tbody.odd tr td{background:#f7f3eb}table .dropdown-menu li{padding:7px 15px;line-height:14px;cursor:pointer}table .col-draggable .draggable-handle{float:left;position:relative;top:0}.not-sort{padding-right:10px}.sort-arrow-asc,.sort-arrow-desc{padding-right:10px;position:relative}.sort-arrow-asc:after,.sort-arrow-desc:after{right:-11px;top:-1px;position:absolute;width:23px}.sort-arrow-asc:hover:after,.sort-arrow-desc:hover:after{color:#dac7a2}.sort-arrow-asc{display:inline-block;text-decoration:none}.sort-arrow-asc:after{font-family:'icons-blank-theme';content:'\e626';font-size:13px;line-height:inherit;color:#f7f3eb;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.sort-arrow-asc:hover:after{color:#dac7a2}.sort-arrow-desc{display:inline-block;text-decoration:none}.sort-arrow-desc:after{font-family:'icons-blank-theme';content:'\e623';font-size:13px;line-height:inherit;color:#f7f3eb;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.sort-arrow-desc:hover:after{color:#dac7a2}.grid-actions .input-text,.pager .input-text,.massaction .input-text,.filter .input-text,.grid-actions select,.pager select,.massaction select,.filter select,.grid-actions .select,.pager .select,.massaction .select,.filter .select{border-color:#989287;box-shadow:none;border-radius:1px;height:28px;margin:0 10px 0 0}.filter th{border:0 solid #676056;padding:6px 3px;vertical-align:top}.filter .ui-datepicker-trigger{cursor:pointer;margin-top:2px}.filter .input-text{padding:0 5px}.filter .range-line:not(:last-child){margin-bottom:5px}.filter .date{padding-right:28px;position:relative;display:inline-block;text-decoration:none}.filter .date .hasDatepicker{vertical-align:top;width:99%}.filter .date img{cursor:pointer;height:25px;width:25px;right:0;position:absolute;vertical-align:middle;z-index:2;opacity:0}.filter .date:before{font-family:'icons-blank-theme';content:'\e612';font-size:42px;line-height:30px;color:#f7f3eb;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.filter .date:hover:before{color:#dac7a2}.filter .date:before{height:29px;margin-left:5px;position:absolute;right:-3px;top:-3px;width:35px}.filter select{border-color:#cac3b4;margin:0;padding:0;width:99%}.filter input.input-text{border-color:#cac3b4;margin:0;width:99%}.filter input.input-text::-webkit-input-placeholder{color:#989287 !important;text-transform:lowercase}.filter input.input-text::-moz-placeholder{color:#989287 !important;text-transform:lowercase}.filter input.input-text:-moz-placeholder{color:#989287 !important;text-transform:lowercase}.filter input.input-text:-ms-input-placeholder{color:#989287 !important;text-transform:lowercase}.grid{background:#fff;color:#676056;font-size:13px;font-weight:400;padding:15px}.grid table{width:100%}.grid tbody tr.selected th,.grid tbody tr.selected td,.grid tbody tr:hover th,.grid tbody tr:hover td,.grid tbody tr:nth-child(odd):hover th,.grid tbody tr:nth-child(odd):hover td{background-color:#f2ebde;cursor:pointer}.grid tbody tr.selected th.empty-text,.grid tbody tr.selected td.empty-text,.grid tbody tr:hover th.empty-text,.grid tbody tr:hover td.empty-text,.grid tbody tr:nth-child(odd):hover th.empty-text,.grid tbody tr:nth-child(odd):hover td.empty-text{background-color:#f7f3eb;cursor:default}.grid .empty-text{font:400 20px/1.2 'Open Sans',sans-serif;text-align:center;white-space:nowrap}.grid .col-sku{max-width:100px;width:100px}.grid .col-select,.grid .col-massaction{text-align:center}.grid .editable .input-text{width:65px}.grid .col-actions .action-select{background:#fff;border-color:#989287;height:28px;margin:0;padding:4px 4px 5px;width:80px}.grid .col-position.editable{white-space:nowrap}.grid .col-position.editable .input-text{margin:-7px 5px 0;width:70%}.eq-ie9 .hor-scroll{display:inline-block;min-height:0;overflow-y:hidden;overflow-x:auto;width:100%}.data-table{border-collapse:separate;width:100%}.data-table thead,.data-table tfoot,.data-table th,.accordion .config .data-table thead th,.accordion .config .data-table tfoot td,.accordion .config .accordion .config .data-table tfoot td th{background:#fff;color:#676056;font-size:13px;font-weight:600}.data-table th{text-align:left}.data-table thead th,.accordion .config .data-table thead th th,.accordion .config .data-table tfoot td th,.accordion .config .accordion .config .data-table tfoot td th th{border:solid #c9c2b8;border-width:0 0 1px;padding:7px}.data-table td,.data-table tbody tr th,.data-table tbody tr td,.accordion .config .data-table td{background:#fff;border-width:0;padding:5px 7px;vertical-align:middle}.data-table tbody tr:nth-child(odd) th,.data-table tbody tr:nth-child(odd) td,.accordion .config .data-table tbody tr:nth-child(odd) td{background:#fbfaf6}.data-table tbody.odd tr th,.data-table tbody.odd tr td{background:#fbfaf6}.data-table tbody.even tr th,.data-table tbody.even tr td{background:#fff}.data-table tfoot tr:last-child th,.data-table tfoot tr:last-child td,.data-table .accordion .config .data-table tfoot tr:last-child td{border:0}.data-table.order-tables tbody td{vertical-align:top}.data-table.order-tables tbody:hover tr th,.data-table.order-tables tbody:hover tr td{background:#f7f3eb}.data-table.order-tables tfoot td{background:#f2ebde;color:#676056;font-size:13px;font-weight:600}.data-table input[type="text"]{width:98%;padding-left:1%;padding-right:1%}.data-table select{margin:0;box-sizing:border-box}.data-table .col-actions .actions-split{margin-top:4px}.data-table .col-actions .actions-split [class^='action-']{background:none;border:1px solid #c8c3b5;padding:3px 5px;color:#bbb3a6;font-size:12px}.data-table .col-actions .actions-split [class^='action-']:first-child{border-right:0}.data-table .col-actions .actions-split .dropdown-menu{margin-top:-1px}.data-table .col-actions .actions-split .dropdown-menu a{display:block;color:#333;text-decoration:none}.data-table .col-actions .actions-split.active .action-toggle{position:relative;border-bottom-right-radius:0;box-shadow:none;background:#fff}.data-table .col-actions .actions-split.active .action-toggle:after{position:absolute;top:100%;left:0;right:0;height:2px;margin-top:-1px;background:#fff;content:'';z-index:2}.data-table .col-actions .actions-split.active .action-toggle .dropdown-menu{border-top-right-radius:0}.data-table .col-default{white-space:nowrap;text-align:center;vertical-align:middle}.data-table .col-delete{text-align:center;width:32px}.data-table .col-file{white-space:nowrap}.data-table .col-file input,.data-table .col-file .input-text{margin:0 5px;width:40%}.data-table .col-file input:first-child,.data-table .col-file .input-text:first-child{margin-left:0}.data-table .col-actions-add{padding:10px 0}.grid-actions{background:#fff;font-size:13px;line-height:28px;padding:10px 15px;position:relative}.grid-actions+.grid{padding-top:5px}.grid-actions .export,.grid-actions .filter-actions{float:right;margin-left:10px;vertical-align:top}.grid-actions .import{display:block;vertical-align:top}.grid-actions .action-reset{background:none;border:0;display:inline;line-height:1.42857143;margin:0;padding:0;color:#1979c3;text-decoration:none;margin:6px 10px 0 0;vertical-align:top}.grid-actions .action-reset:visited{color:purple;text-decoration:none}.grid-actions .action-reset:hover{color:#006bb4;text-decoration:underline}.grid-actions .action-reset:active{color:#ff5501;text-decoration:underline}.grid-actions .action-reset:hover{color:#006bb4}.grid-actions .action-reset:hover,.grid-actions .action-reset:active,.grid-actions .action-reset:focus{background:none;border:0}.grid-actions .action-reset.disabled,.grid-actions .action-reset[disabled],fieldset[disabled] .grid-actions .action-reset{color:#1979c3;text-decoration:underline;cursor:default;pointer-events:none;opacity:.5}.grid-actions .import .label,.grid-actions .export .label,.massaction>.entry-edit .label{margin:0 14px 0 0;vertical-align:inherit}.grid-actions .import .action-,.grid-actions .export .action-,.grid-actions .filter-actions .action-,.massaction>.entry-edit .action-{vertical-align:inherit}.grid-actions .filter .date{float:left;margin:0 15px 0 0;position:relative}.grid-actions .filter .date:before{color:#676056;top:1px}.grid-actions .filter .date:hover:before{color:#31302b}.grid-actions .filter .label{margin:0}.grid-actions .filter .hasDatepicker{margin:0 5px;width:80px}.grid-actions .filter .show-by .select{margin-left:5px;padding:4px 4px 5px;vertical-align:top;width:auto}.grid-actions .filter.required:after{content:''}.grid-actions img{vertical-align:middle;height:22px;width:22px}.grid-actions .validation-advice{background:#f9d4d4;border:1px solid #e22626;border-radius:3px;color:#e22626;margin:5px 0 0;padding:3px 7px;position:absolute;white-space:nowrap;z-index:5}.grid-actions .validation-advice:before{width:0;height:0;border:5px solid transparent;border-bottom-color:#e22626;content:'';left:50%;margin-left:-5px;position:absolute;top:-11px}.grid-actions input[type="text"].validation-failed{border-color:#e22626;box-shadow:0 0 8px rgba(226,38,38,.6)}.grid-actions .link-feed{white-space:nowrap}.pager{font-size:13px}.grid .pager{margin:15px 0 0;position:relative;text-align:center}.pager .pages-total-found{margin-right:25px}.pager .view-pages .select{margin:0 5px}.pager .link-feed{font-size:12px;margin:7px 15px 0 0;position:absolute;right:0;top:0}.pager .action-previous,.pager .action-next{background:none;border:0;display:inline;line-height:1.42857143;margin:0;padding:0;color:#1979c3;text-decoration:none;line-height:.6;overflow:hidden;width:20px}.pager .action-previous:visited,.pager .action-next:visited{color:purple;text-decoration:none}.pager .action-previous:hover,.pager .action-next:hover{color:#006bb4;text-decoration:underline}.pager .action-previous:active,.pager .action-next:active{color:#ff5501;text-decoration:underline}.pager .action-previous:hover,.pager .action-next:hover{color:#006bb4}.pager .action-previous:hover,.pager .action-next:hover,.pager .action-previous:active,.pager .action-next:active,.pager .action-previous:focus,.pager .action-next:focus{background:none;border:0}.pager .action-previous.disabled,.pager .action-next.disabled,.pager .action-previous[disabled],.pager .action-next[disabled],fieldset[disabled] .pager .action-previous,fieldset[disabled] .pager .action-next{color:#1979c3;text-decoration:underline;cursor:default;pointer-events:none;opacity:.5}.pager .action-previous:before,.pager .action-next:before{margin-left:-10px}.pager .action-previous.disabled,.pager .action-next.disabled{opacity:.3}.pager .action-previous{display:inline-block;text-decoration:none}.pager .action-previous>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.pager .action-previous>span.focusable:active,.pager .action-previous>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-previous>span.focusable:active,.pager .action-previous>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-previous:before{font-family:'icons-blank-theme';content:'\e617';font-size:40px;line-height:inherit;color:#026294;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.pager .action-previous:hover:before{color:#007dbd}.pager .action-next{display:inline-block;text-decoration:none}.pager .action-next>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.pager .action-next>span.focusable:active,.pager .action-next>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-next>span.focusable:active,.pager .action-next>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-next:before{font-family:'icons-blank-theme';content:'\e608';font-size:40px;line-height:inherit;color:#026294;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.pager .action-next:hover:before{color:#007dbd}.pager .input-text{height:25px;line-height:16px;margin-right:5px;text-align:center;width:25px;vertical-align:top}.pager .pages-total{line-height:25px;vertical-align:top}.massaction{background:#fff;border-top:1px solid #f2ebde;font-size:13px;line-height:28px;padding:15px 15px 0}.massaction>.entry-edit{float:right}.massaction>.entry-edit .field-row{display:inline-block;vertical-align:top}.massaction>.entry-edit .validation-advice{display:none !important}.massaction>.entry-edit .form-inline{display:inline-block}.massaction>.entry-edit .label{padding:0;width:auto}.massaction>.entry-edit .action-{vertical-align:top}.massaction .select.validation-failed{border:1px dashed #e22626;background:#f9d4d4}.grid-severity-critical,.grid-severity-major,.grid-severity-notice,.grid-severity-minor{background:#feeee1;border:1px solid #ed4f2e;color:#ed4f2e;display:block;padding:0 3px;font-weight:700;line-height:17px;text-transform:uppercase;text-align:center}.grid-severity-critical,.grid-severity-major{border-color:#e22626;background:#f9d4d4;color:#e22626}.grid-severity-notice{border-color:#5b8116;background:#d0e5a9;color:#185b00}.grid tbody td input[type="text"],.data-table tbody td input[type="text"],.grid tbody th input[type="text"],.data-table tbody th input[type="text"],.grid tbody td .input-text,.data-table tbody td .input-text,.grid tbody th .input-text,.data-table tbody th .input-text,.grid tbody td select,.data-table tbody td select,.grid tbody th select,.data-table tbody th select,.grid tbody td .select,.data-table tbody td .select,.grid tbody th .select,.data-table tbody th .select{width:99%}.ui-tabs-panel .grid .col-sku{max-width:150px;width:150px}.col-indexer_status,.col-indexer_mode{width:160px}.fieldset-wrapper .grid-actions+.grid{padding-top:15px}.fieldset-wrapper .grid-actions{padding:10px 0 0}.fieldset-wrapper .grid{padding:0}.fieldset-wrapper .massaction{padding:0;border-top:none;margin-bottom:15px}.accordion .grid{padding:0}.ui-dialog-content .grid-actions,.ui-dialog-content .grid{padding-left:0;padding-right:0}.qty-table td{border:0;padding:0 5px 3px}.sales-order-create-index .sales-order-create-index .grid table .action-configure{float:right}.sales-order-create-index .data-table .border td{padding-bottom:15px}.sales-order-create-index .actions.update{margin:10px 0}.adminhtml-order-shipment-new .grid .col-product{max-width:770px;width:770px}.customer-index-index .grid .col-name{max-width:90px;width:90px}.customer-index-index .grid .col-billing_region{width:70px}.adminhtml-cms-hierarchy-index .col-title,.adminhtml-cms-hierarchy-index .col-identifier{max-width:410px;width:410px}.adminhtml-widget-instance-edit .grid-chooser .control{margin-top:-19px;width:80%}.eq-ie9 .adminhtml-widget-instance-edit .grid-chooser .control{margin-top:-18px}.adminhtml-widget-instance-edit .grid-chooser .control .grid-actions{padding:0 0 15px}.adminhtml-widget-instance-edit .grid-chooser .control .grid{padding:0}.adminhtml-widget-instance-edit .grid-chooser .control .addon input:last-child,.adminhtml-widget-instance-edit .grid-chooser .control .addon select:last-child{border-radius:0}.reports-report-product-sold .grid .col-name{max-width:720px;width:720px}.adminhtml-system-store-index .grid td{max-width:310px}.adminhtml-system-currency-index .grid{padding-top:0}.adminhtml-system-currency-index .col-currency-edit-rate{min-width:40px}.adminhtml-system-currency-index .col-base-currency{font-weight:700}.adminhtml-system-currency-index .old-rate{display:block;margin-top:3px;text-align:center}.adminhtml-system-currency-index .hor-scroll{overflow-x:auto;min-width:970px}.adminhtml-system-currencysymbol-index .col-currency{width:35%}.adminhtml-system-currencysymbol-index .grid .input-text{margin:0 10px 0 0;width:50%}.catalog-product-set-index .col-set_name{max-width:930px;width:930px}.adminhtml-export-index .grid td{vertical-align:middle}.adminhtml-export-index .grid .input-text-range{margin:0 10px 0 5px;width:37%}.adminhtml-export-index .grid .input-text-range-date{margin:0 5px;width:32%}.adminhtml-export-index .ui-datepicker-trigger{display:inline-block;margin:-3px 10px 0 0;vertical-align:middle}.adminhtml-notification-index .grid .col-select,.adminhtml-cache-index .grid .col-select,.adminhtml-process-list .grid .col-select,.indexer-indexer-list .grid .col-select{width:10px}@font-face{font-family:'icons-blank-theme';src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot');src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot?#iefix') format('embedded-opentype'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff2') format('woff2'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff') format('woff'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.ttf') format('truetype'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.svg#icons-blank-theme') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'icons-blank-theme';src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot');src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot?#iefix') format('embedded-opentype'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff2') format('woff2'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff') format('woff'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.ttf') format('truetype');font-weight:400;font-style:normal}.navigation{background-color:#676056;position:relative;z-index:5}.navigation .level-0.reverse>.submenu{right:1px}.navigation>ul{position:relative;text-align:right}.navigation .level-0>.submenu{display:none;position:absolute;top:100%;padding:19px 13px}.navigation .level-0>.submenu a{display:block;color:#676056;font-size:13px;font-weight:400;line-height:1.385;padding:3px 12px 3px;text-decoration:none}.navigation .level-0>.submenu a:focus,.navigation .level-0>.submenu a:hover{text-decoration:underline}.navigation .level-0>.submenu a:hover{color:#fff;background:#989287;text-decoration:none}.navigation .level-0>.submenu li{margin-bottom:1px}.navigation .level-0>.submenu a[href="#"]{cursor:default;display:block;color:#676056;font-size:14px;font-weight:700;line-height:1;margin:7px 0 6px;padding:0 12px}.navigation .level-0>.submenu a[href="#"]:focus,.navigation .level-0>.submenu a[href="#"]:hover{color:#676056;font-size:14px;font-weight:700;background:none;text-decoration:none}.navigation .level-0{display:inline-block;float:left;text-align:left;transition:display .15s ease-out}.navigation .level-0>a{background:none;display:block;padding:12px 13px 0;color:#f2ebde;font-size:13px;font-weight:600;text-transform:uppercase;text-decoration:none;transition:background .15s ease-out}.navigation .level-0>a:after{content:"";display:block;margin-top:10px;height:3px;font-size:0}.navigation .level-0.active>a{font-weight:700}.navigation .level-0.active>a:after{background:#ef672f}.navigation .level-0.hover.recent>a{background:#fff;color:#676056;font-size:13px;font-weight:600}.navigation .level-0.hover.recent>a:after{background:none}.navigation .level-0.hover.recent.active>a{font-weight:700}.navigation .level-0>.submenu{opacity:0;visibility:hidden}.navigation .level-0.recent.hover>.submenu{opacity:1;visibility:visible}.no-js .navigation .level-0:hover>.submenu,.no-js .navigation .level-0.hover>.submenu,.no-js .navigation .level-0>a:focus+.submenu{display:block}.navigation .level-0>.submenu{background:#fff;box-shadow:0 3px 3px rgba(50,50,50,.15)}.navigation .level-0>.submenu li{max-width:200px}.navigation .level-0>.submenu>ul{white-space:nowrap}.navigation .level-0>.submenu .column{display:inline-block;margin-left:40px;vertical-align:top}.navigation .level-0>.submenu .column:first-child{margin-left:0}.navigation .level-0 .submenu .level-1{white-space:normal}.navigation .level-0.parent .submenu .level-1.parent{margin:17px 0 25px}.navigation .level-0.parent .level-1.parent:first-child{margin-top:0}.navigation .level-2 .submenu{margin-left:7px}.navigation .level-0>.submenu .level-2>a[href="#"]{font-size:13px;margin-top:10px;margin-left:7px}.navigation .level-2>.submenu a{font-size:12px;line-height:1.231}.navigation .level-0>.submenu .level-3>a[href="#"],.navigation .level-3 .submenu{margin-left:15px}.navigation .level-0.item-system,.navigation .level-0.item-stores{float:none}.navigation .level-0.item-system>.submenu,.navigation .level-0.item-stores>.submenu{left:auto;right:1px}.adminhtml-dashboard-index .col-1-layout{max-width:1300px;border:none;border-radius:0;padding:0;background:#f7f3eb}.dashboard-inner{padding-top:35px}.dashboard-inner:before,.dashboard-inner:after{content:"";display:table}.dashboard-inner:after{clear:both}.dashboard-inner:before,.dashboard-inner:after{content:"";display:table}.dashboard-inner:after{clear:both}.dashboard-secondary{float:left;width:32%;margin:0 1.5%}.dashboard-main{float:right;width:65%}.dashboard-diagram-chart{max-width:100%;height:auto}.dashboard-diagram-nodata,.dashboard-diagram-switcher{padding:20px 0}.dashboard-diagram-image{background:#fff url(../mui/images/ajax-loader-small.gif) no-repeat 50% 50%}.dashboard-container .ui-tabs-panel{background-color:#fff;min-height:40px;padding:15px}.dashboard-store-stats{margin-top:35px}.dashboard-store-stats .ui-tabs-panel{background:#fff url(../mui/images/ajax-loader-small.gif) no-repeat 50% 50%}.dashboard-item{margin-bottom:30px}.dashboard-item-header{margin-left:5px}.dashboard-item.dashboard-item-primary{margin-bottom:35px}.dashboard-item.dashboard-item-primary .title{font-size:22px;margin-bottom:5px}.dashboard-item.dashboard-item-primary .dashboard-sales-value{display:block;text-align:right;font-weight:600;font-size:30px;margin-right:12px;padding-bottom:5px}.dashboard-item.dashboard-item-primary:first-child{color:#ef672f}.dashboard-item.dashboard-item-primary:first-child .title{color:#ef672f}.dashboard-totals{background:#fff;padding:50px 15px 25px}.dashboard-totals-list{margin:0;padding:0;list-style:none none}.dashboard-totals-list:before,.dashboard-totals-list:after{content:"";display:table}.dashboard-totals-list:after{clear:both}.dashboard-totals-list:before,.dashboard-totals-list:after{content:"";display:table}.dashboard-totals-list:after{clear:both}.dashboard-totals-item{float:left;width:18%;margin-left:7%;padding-top:15px;border-top:2px solid #cac3b4}.dashboard-totals-item:first-child{margin-left:0}.dashboard-totals-label{display:block;font-size:16px;font-weight:600;padding-bottom:2px}.dashboard-totals-value{color:#ef672f;font-size:20px}.dashboard-data{width:100%}.dashboard-data thead{background:transparent}.dashboard-data thead tr{background:none}.dashboard-data th,.dashboard-data td{border:none;padding:10px 12px;text-align:right}.dashboard-data th:first-child,.dashboard-data td:first-child{text-align:left}.dashboard-data th{color:#676056;font-weight:600}.dashboard-data td{background-color:transparent}.dashboard-data tbody tr:hover td{background-color:transparent}.dashboard-data tbody tr:nth-child(odd) td,.dashboard-data tbody tr:nth-child(odd):hover td,.dashboard-data tbody tr:nth-child(odd) th,.dashboard-data tbody tr:nth-child(odd):hover th{background-color:#e1dbcf}.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd) td,.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd):hover td,.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd) th,.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd):hover th{background-color:#f7f3eb}.dashboard-data td.empty-text{text-align:center}.ui-tabs-panel .dashboard-data{background-color:#fff}.mage-dropdown-dialog.ui-dialog .ui-dialog-content{overflow:visible}.mage-dropdown-dialog.ui-dialog .ui-dialog-buttonpane{padding:0}.message-system-inner{background:#f7f3eb;border:1px solid #c0bbaf;border-top:0;border-radius:0 0 5px 5px;float:right;overflow:hidden}.message-system-unread .message-system-inner{float:none}.message-system-list{margin:0;padding:0;list-style:none;float:left}.message-system .message-system-list{width:75%}.message-system-list li{padding:5px 13px 7px 36px;position:relative}.message-system-short{padding:5px 13px 7px;float:right}.message-system-short span{display:inline-block;margin-left:7px;border-left:1px #d1ccc3 solid}.message-system-short span:first-child{border:0;margin-left:0}.message-system-short a{padding-left:27px;position:relative;height:16px}.message-system .message-system-short a:before,.message-system-list li:before{font-family:'MUI-Icons';font-style:normal;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;height:16px;width:16px;font-size:16px;line-height:16px;text-align:center;position:absolute;left:7px;top:2px}.message-system-list li:before{top:5px;left:13px}.message-system .message-system-short .warning a:before,.message-system-list li.warning:before{content:"\e006";color:#f2a825}.message-system .message-system-short .error a:before,.message-system-list li.error:before{content:"\e086";font-family:'MUI-Icons';color:#c00815}.ui-dialog .message-system-list{margin-bottom:25px}.sales-order-create-index .order-errors .notice{color:#ed4f2e;font-size:11px;margin:5px 0 0}.order-errors .fieldset-wrapper-title .title{box-sizing:border-box;background:#fffbf0;border:1px solid #d87e34;border-radius:5px;color:#676056;font-size:14px;margin:20px 0;padding:10px 26px 10px 35px;position:relative}.order-errors .fieldset-wrapper-title .title:before{position:absolute;left:11px;top:50%;margin-top:-11px;width:auto;height:auto;font-family:'MUI-Icons';font-style:normal;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;font-size:16px;line-height:inherit;content:'\e046';color:#d87e34}.search-global.miniform{position:relative;z-index:1000;display:inline-block;vertical-align:top;margin:6px 10px 0}.search-global.miniform .mage-suggest{border:0;border-radius:0}.search-global-actions{display:none}.search-global-field{margin:0}.search-global-field .label{position:absolute;right:4px;z-index:2;cursor:pointer;display:inline-block;text-decoration:none}.search-global-field .label>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.search-global-field .label>span.focusable:active,.search-global-field .label>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.search-global-field .label>span.focusable:active,.search-global-field .label>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.search-global-field .label:before{font-family:'MUI-Icons';content:"\e01f";font-size:18px;line-height:29px;color:#cac3b4;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.search-global-field .control{width:48px;overflow:hidden;opacity:0;transition:all .3s ease}.search-global-field .control input[type="text"]{background:transparent;border:none;width:100%}.search-global-field.active{z-index:2}.search-global-field.active .label:before{display:none}.search-global-field.active .control{overflow:visible;opacity:1;transition:all .3s ease;width:300px}.search-global-menu{box-sizing:border-box;display:block;width:100%}.notifications-summary{display:inline-block;text-align:left;position:relative;z-index:1}.notifications-summary.active{z-index:999}.notifications-action{color:#f2ebde;padding:12px 22px 11px;text-transform:capitalize;display:inline-block;text-decoration:none}.notifications-action:before{font-family:"MUI-Icons";content:"\e06e";font-size:18px;line-height:18px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.notifications-action:visited,.notifications-action:focus,.notifications-action:active,.notifications-action:hover{color:#f2ebde;text-decoration:none}.notifications-action.active{background-color:#FFF;color:#676056}.notifications-action .text{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.notifications-action .text.focusable:active,.notifications-action .text.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-action .text.focusable:active,.notifications-action .text.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-action .qty.counter{display:inline-block;background:#ed4f2e;color:#f2ebde;font-size:12px;line-height:12px;font-weight:700;padding:1px 3px;position:absolute;top:6px;left:50%;border-radius:4px}.notifications-list{width:300px;padding:0;margin:0}.notifications-list .last{padding:10px;text-align:center;font-size:12px}.notifications-summary .notifications-entry{padding:15px;color:#676056;font-size:11px;font-weight:400}.notifications-entry{position:relative;z-index:1}.notifications-entry:hover .action{display:block}.notifications-entry-title{padding-right:15px;color:#ed4f2e;font-size:12px;font-weight:600;display:block;margin-bottom:10px}.notifications-entry-description{line-height:1.3;display:block;max-height:3.9em;overflow:hidden;margin-bottom:10px;text-overflow:ellipsis}.notifications-close.action{position:absolute;z-index:1;top:12px;right:12px;display:inline-block;background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400;display:none}.notifications-close.action>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.notifications-close.action>span.focusable:active,.notifications-close.action>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-close.action>span.focusable:active,.notifications-close.action>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-close.action:before{font-family:'MUI-Icons';content:"\e07f";font-size:16px;line-height:inherit;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.notifications-close.action:focus,.notifications-close.action:active{background:none;border:none}.notifications-close.action:hover{background:none;border:none}.notifications-close.action.disabled,.notifications-close.action[disabled],fieldset[disabled] .notifications-close.action{cursor:not-allowed;pointer-events:none;opacity:.5}.notifications-dialog-content{display:none}.notifications-critical .notifications-entry-title{padding-left:25px;display:inline-block;text-decoration:none}.notifications-critical .notifications-entry-title:before{font-family:'MUI-Icons';content:"\e086";font-size:18px;line-height:18px;color:#c00815;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.notifications-critical .notifications-entry-title:before{position:absolute;margin-left:-25px}.notifications-dialog-content .notifications-entry-time{color:#8c867e;font-size:13px;font-family:Helvetica,Arial,sans-serif;position:absolute;right:17px;bottom:27px;text-align:right}.notifications-url{display:inline-block;text-decoration:none}.notifications-url>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.notifications-url>span.focusable:active,.notifications-url>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-url>span.focusable:active,.notifications-url>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-url:after{font-family:'MUI-Icons';content:"\e084";font-size:16px;line-height:inherit;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:-2px 0 0 10px}.notifications-dialog-content .notifications-entry-title{font-size:15px}.locale-switcher-field{white-space:nowrap;float:left}.locale-switcher-field .control,.locale-switcher-field .label{vertical-align:middle;margin:0 10px 0 0;display:inline-block}.locale-switcher-select{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;border:1px solid #ada89e;max-width:200px;height:31px;background:url("../images/select-bg.svg") no-repeat 100% 50%;background-size:30px 60px;padding-right:29px;text-indent:.01px;text-overflow:''}.locale-switcher-select::-ms-expand{display:none}.lt-ie10 .locale-switcher-select{background-image:none;padding-right:4px}@-moz-document url-prefix(){.locale-switcher-select{background-image:none}}@-moz-document url-prefix(){.locale-switcher-select{background-image:none}}.mage-suggest{text-align:left;box-sizing:border-box;position:relative;display:inline-block;vertical-align:top;width:100%;background-color:#fff;border:1px solid #ada89e;border-radius:2px}.mage-suggest:after{position:absolute;top:3px;right:3px;bottom:0;width:22px;text-align:center;font-family:'MUI-Icons';font-style:normal;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;content:'\e01f';font-size:18px;color:#b2b2b2}.mage-suggest input[type="search"],.mage-suggest input.search{width:100%;border:none;background:none;padding-right:30px}.mage-suggest.category-select input[type="search"],.mage-suggest.category-select input.search{height:26px}.mage-suggest-dropdown{position:absolute;left:0;right:0;top:100%;margin:1px -1px 0;border:1px solid #cac2b5;background:#fff;box-shadow:0 2px 4px rgba(0,0,0,.2);z-index:990}.mage-suggest-dropdown ul{margin:0;padding:0;list-style:none}.mage-suggest-dropdown li{border-bottom:1px solid #e5e5e5;padding:0}.mage-suggest-dropdown li a{display:block}.mage-suggest-dropdown li a.ui-state-focus{background:#f5f5f5}.mage-suggest-dropdown li a,.mage-suggest-dropdown .jstree li a:hover,.mage-suggest-dropdown .jstree .jstree-hovered,.mage-suggest-dropdown .jstree .jstree-clicked{padding:6px 12px 5px;text-decoration:none;color:#333}.mage-suggest-dropdown .jstree li a:hover,.mage-suggest-dropdown .jstree .jstree-hovered,.mage-suggest-dropdown .jstree .jstree-clicked{border:none}.mage-suggest-dropdown .jstree li{border-bottom:0}.mage-suggest-dropdown .jstree li a{display:inline-block}.mage-suggest-dropdown .jstree .mage-suggest-selected>a{color:#000;background:#f1ffeb}.field-category_ids .mage-suggest-dropdown,.field-new_category_parent .mage-suggest-dropdown{max-height:200px;overflow:auto}.mage-suggest-dropdown .jstree .mage-suggest-selected>a:hover,.mage-suggest-dropdown .jstree .mage-suggest-selected>.jstree-hovered,.mage-suggest-dropdown .jstree .mage-suggest-selected>.jstree-clicked,.mage-suggest-dropdown .jstree .mage-suggest-selected.mage-suggest-not-active>.jstree-hovered,.mage-suggest-dropdown .jstree .mage-suggest-selected.mage-suggest-not-active>.jstree-clicked{background:#e5ffd9}.mage-suggest-dropdown .jstree .mage-suggest-not-active>a{color:#d4d4d4}.mage-suggest-dropdown .jstree .mage-suggest-not-active>a:hover,.mage-suggest-dropdown .jstree .mage-suggest-not-active>.jstree-hovered,.mage-suggest-dropdown .jstree .mage-suggest-not-active>.jstree-clicked{background:#f5f5f5}.mage-suggest-dropdown .category-path{font-size:11px;margin-left:10px;color:#9ba8b5}.suggest-expandable .action-dropdown .action-toggle{display:inline-block;max-width:500px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:none;border:none;box-shadow:none;color:#676056;font-size:12px;padding:5px 4px;filter:none}.suggest-expandable .action-dropdown .action-toggle span{display:inline}.suggest-expandable .action-dropdown .action-toggle:before{display:inline-block;float:right;margin-left:4px;font-size:13px;color:#b2b0ad}.suggest-expandable .action-dropdown .action-toggle:hover:before{color:#7e7e7e}.suggest-expandable .dropdown-menu{margin:1px 0 0;left:0;right:auto;width:245px;z-index:4}.suggest-expandable .mage-suggest{border:none;border-radius:3px 3px 0 0}.suggest-expandable .mage-suggest:after{top:10px;right:8px}.suggest-expandable .mage-suggest-inner .title{margin:0;padding:0 10px 4px;text-transform:uppercase;color:#a6a098;font-size:12px;border-bottom:1px solid #e5e5e5}.suggest-expandable .mage-suggest-inner>input[type="search"],.suggest-expandable .mage-suggest-inner>input.search{position:relative;margin:6px 5px 5px;padding-right:20px;border:1px solid #ada89e;width:236px;z-index:1}.suggest-expandable .mage-suggest-inner>input.ui-autocomplete-loading,.suggest-expandable .mage-suggest-inner>input.mage-suggest-state-loading{background:#fff url("../mui/images/ajax-loader-small.gif") no-repeat 190px 50%}.suggest-expandable .mage-suggest-dropdown{margin-top:0;border-top:0;border-radius:0 0 3px 3px;max-height:300px;overflow:auto;width:100%;float:left}.suggest-expandable .mage-suggest-dropdown ul{margin:0;padding:0;list-style:none}.suggest-expandable .action-show-all:hover,.suggest-expandable .action-show-all:active,.suggest-expandable .action-show-all:focus,.suggest-expandable .action-show-all[disabled]{border-top:1px solid #e5e5e5;display:block;width:100%;padding:8px 10px 10px;text-align:left;font:12px/1.333 Arial,Verdana,sans-serif;color:#676056}.product-actions .suggest-expandable{max-width:500px;float:left;margin-top:1px}.page-actions.fixed #product-template-suggest-container{display:none}.catalog-category-edit .col-2-left-layout:before{display:none}.category-content .ui-tabs-panel .fieldset{padding-top:40px}.category-content .ui-tabs-panel .fieldset .legend{display:none}.attributes-edit-form .field:not(.field-weight) .addon{display:block;position:relative}.attributes-edit-form .field:not(.field-weight) .addon input[type="text"]{border-width:1px}.attributes-edit-form .field:not(.field-weight) .addon .addafter{display:block;border:0;height:auto;width:auto}.attributes-edit-form .field:not(.field-weight) .addon input:focus~.addafter{box-shadow:none}.attributes-edit-form .with-addon .textarea{margin:0}.attributes-edit-form .attribute-change-checkbox{display:block;margin-top:5px}.attributes-edit-form .attribute-change-checkbox .label{float:none;padding:0;width:auto}.attributes-edit-form .attribute-change-checkbox .checkbox{margin-right:5px;width:auto}.attributes-edit-form .field-price .addon>input,.attributes-edit-form .field-special_price .addon>input,.attributes-edit-form .field-gift_wrapping_price .addon>input,.attributes-edit-form .field-msrp .addon>input,.attributes-edit-form .field-gift_wrapping_price .addon>input{padding-left:23px}.attributes-edit-form .field-price .addafter>strong,.attributes-edit-form .field-special_price .addafter>strong,.attributes-edit-form .field-gift_wrapping_price .addafter>strong,.attributes-edit-form .field-msrp .addafter>strong,.attributes-edit-form .field-gift_wrapping_price .addafter>strong{left:5px;position:absolute;top:3px}.attributes-edit-form .field.type-price input:focus+label,.attributes-edit-form .field-price input:focus+label,.attributes-edit-form .field-special_price input:focus+label,.attributes-edit-form .field-msrp input:focus+label,.attributes-edit-form .field-weight input:focus+label{box-shadow:none}.attributes-edit-form .field-special_from_date>.control .input-text,.attributes-edit-form .field-special_to_date>.control .input-text,.attributes-edit-form .field-news_from_date>.control .input-text,.attributes-edit-form .field-news_to_date>.control .input-text,.attributes-edit-form .field-custom_design_from>.control .input-text,.attributes-edit-form .field-custom_design_to>.control .input-text{border-width:1px;width:130px}.attributes-edit-form .field-weight .fields-group-2 .control{padding-right:27px}.attributes-edit-form .field-weight .fields-group-2 .control .addafter+.addafter{border-width:1px 1px 1px 0;border-style:solid;height:28px;right:0;position:absolute;top:0}.attributes-edit-form .field-weight .fields-group-2 .control .addafter strong{line-height:28px}.attributes-edit-form .field-weight .fields-group-2 .control>input:focus+.addafter+.addafter{box-shadow:0 0 8px rgba(82,168,236,.6)}.attributes-edit-form .field-gift_message_available .addon>input[type="checkbox"],.attributes-edit-form .field-gift_wrapping_available .addon>input[type="checkbox"]{width:auto;margin-right:5px}.attributes-edit-form .fieldset>.addafter{display:none}.advanced-inventory-edit .field.choice{display:block;margin:3px 0 0}.advanced-inventory-edit .field.choice .label{padding-top:1px}.product-actions:before,.product-actions:after{content:"";display:table}.product-actions:after{clear:both}.product-actions:before,.product-actions:after{content:"";display:table}.product-actions:after{clear:both}.product-actions .switcher{float:right}#configurable-attributes-container .actions-select{display:inline-block;position:relative}#configurable-attributes-container .actions-select:before,#configurable-attributes-container .actions-select:after{content:"";display:table}#configurable-attributes-container .actions-select:after{clear:both}#configurable-attributes-container .actions-select:before,#configurable-attributes-container .actions-select:after{content:"";display:table}#configurable-attributes-container .actions-select:after{clear:both}#configurable-attributes-container .actions-select .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}#configurable-attributes-container .actions-select .action.toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:22px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#configurable-attributes-container .actions-select .action.toggle:hover:after{color:inherit}#configurable-attributes-container .actions-select .action.toggle:active:after{color:inherit}#configurable-attributes-container .actions-select .action.toggle.active{display:inline-block;text-decoration:none}#configurable-attributes-container .actions-select .action.toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:22px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#configurable-attributes-container .actions-select .action.toggle.active:hover:after{color:inherit}#configurable-attributes-container .actions-select .action.toggle.active:active:after{color:inherit}#configurable-attributes-container .actions-select ul.dropdown{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px solid #bbb;position:absolute;z-index:100;top:100%;min-width:100%;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}#configurable-attributes-container .actions-select ul.dropdown li{margin:0;padding:3px 5px}#configurable-attributes-container .actions-select ul.dropdown li:hover{background:#e8e8e8;cursor:pointer}#configurable-attributes-container .actions-select.active{overflow:visible}#configurable-attributes-container .actions-select.active ul.dropdown{display:block}#configurable-attributes-container .actions-select .action.toggle{padding:1px 8px;border:1px solid #ada89e;background:#fff;border-radius:0 2px 2px 0}#configurable-attributes-container .actions-select .action.toggle:after{width:14px;text-indent:-2px}#configurable-attributes-container .actions-select ul.dropdown li:hover{background:#eef8fc}#configurable-attributes-container .actions-select ul.dropdown a{color:#333;text-decoration:none}#product-variations-matrix .actions-image-uploader{display:inline-block;position:relative;display:block;width:50px}#product-variations-matrix .actions-image-uploader:before,#product-variations-matrix .actions-image-uploader:after{content:"";display:table}#product-variations-matrix .actions-image-uploader:after{clear:both}#product-variations-matrix .actions-image-uploader:before,#product-variations-matrix .actions-image-uploader:after{content:"";display:table}#product-variations-matrix .actions-image-uploader:after{clear:both}#product-variations-matrix .actions-image-uploader .action.split{float:left;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle{float:right;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle{padding:6px 5px;display:inline-block;text-decoration:none}#product-variations-matrix .actions-image-uploader .action.toggle>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle:hover:after{color:inherit}#product-variations-matrix .actions-image-uploader .action.toggle:active:after{color:inherit}#product-variations-matrix .actions-image-uploader .action.toggle.active{display:inline-block;text-decoration:none}#product-variations-matrix .actions-image-uploader .action.toggle.active>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle.active:hover:after{color:inherit}#product-variations-matrix .actions-image-uploader .action.toggle.active:active:after{color:inherit}#product-variations-matrix .actions-image-uploader ul.dropdown{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px solid #bbb;position:absolute;z-index:100;top:100%;min-width:100%;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}#product-variations-matrix .actions-image-uploader ul.dropdown li{margin:0;padding:3px 5px}#product-variations-matrix .actions-image-uploader ul.dropdown li:hover{background:#e8e8e8;cursor:pointer}#product-variations-matrix .actions-image-uploader.active{overflow:visible}#product-variations-matrix .actions-image-uploader.active ul.dropdown{display:block}#product-variations-matrix .actions-image-uploader .action.toggle{padding:0 2px;border:1px solid #b7b2a7;background:#fff;border-radius:0 4px 4px 0;border-left:none;height:33px}#product-variations-matrix .actions-image-uploader .action.toggle.no-display{display:none}#product-variations-matrix .actions-image-uploader .action.toggle:after{width:12px;text-indent:-5px}#product-variations-matrix .actions-image-uploader ul.dropdown{left:0;margin-left:0;width:100px}#product-variations-matrix .actions-image-uploader ul.dropdown li:hover{background:#eef8fc}#product-variations-matrix .actions-image-uploader ul.dropdown a{color:#333;text-decoration:none}.debugging-hints .page-actions{position:relative;z-index:1}.debugging-hints .page-actions .debugging-hint-template-file{left:auto !important;right:0 !important}.filter-segments{list-style:none;padding:0}.adminhtml-report-customer-test-detail .col-id{width:35px}.adminhtml-report-customer-test-detail .col-period{white-space:nowrap;width:70px}.adminhtml-report-customer-test-detail .col-zip{width:50px}.adminhtml-report-customer-test-segment .col-id{width:35px}.adminhtml-report-customer-test-segment .col-status{width:65px}.adminhtml-report-customer-test-segment .col-qty{width:145px}.adminhtml-report-customer-test-segment .col-segment,.adminhtml-report-customer-test-segment .col-website{width:35%}.adminhtml-report-customer-test-segment .col-select{width:45px}.test-custom-attributes{margin-bottom:20px}.adminhtml-test-index th.col-id{text-align:left}.adminhtml-test-index .col-price{text-align:right;width:50px}.adminhtml-test-index .col-actions{width:50px}.adminhtml-test-index .col-select{width:60px}.adminhtml-test-edit .field-image .control{line-height:28px}.adminhtml-test-edit .field-image a{display:inline-block;margin:0 5px 0 0}.adminhtml-test-edit .field-image img{vertical-align:middle}.adminhtml-test-new .field-image .input-file,.adminhtml-test-edit .field-image .input-file{display:inline-block;margin:0 15px 0 0;width:auto}.adminhtml-test-new .field-image .addafter,.adminhtml-test-edit .field-image .addafter{border:0;box-shadow:none;display:inline-block;margin:0 15px 0 0;height:auto;width:auto}.adminhtml-test-new .field-image .delete-image,.adminhtml-test-edit .field-image .delete-image{display:inline-block;white-space:nowrap}.adminhtml-test-edit .field-image .delete-image input{margin:-3px 5px 0 0;width:auto;display:inline-block}.adminhtml-test-edit .field-image .addon .delete-image input:focus+label{border:0;box-shadow:none}.adminhtml-test-index .col-id{width:35px}.adminhtml-test-index .col-status{white-space:normal;width:75px}.adminhtml-test-index .col-websites{white-space:nowrap;width:200px}.adminhtml-test-index .col-price .label{display:inline-block;min-width:60px;white-space:nowrap}.adminhtml-test-index .col-price .price-excl-tax .price,.adminhtml-test-index .col-price .price-incl-tax .price{font-weight:700}.invitee_information,.inviter_information{width:48.9362%}.invitee_information{float:left}.inviter_information{float:right}.test_information .data-table th,.invitee_information .data-table th,.inviter_information .data-table th{width:20%;white-space:nowrap}.test_information .data-table textarea,.test_information .data-table input{width:100%}.tests-history ul{margin:0;padding-left:25px}.tests-history ul .status:before{display:inline-block;content:"|";margin:0 10px}.adminhtml-report-test-order .col-period{white-space:nowrap;width:70px}.adminhtml-report-test-order .col-inv-sent,.adminhtml-report-test-order .col-inv-acc,.adminhtml-report-test-order .col-acc,.adminhtml-report-test-order .col-rate{text-align:right;width:23%}.adminhtml-report-test-customer .col-id{width:35px}.adminhtml-report-test-customer .col-period{white-space:nowrap;width:70px}.adminhtml-report-test-customer .col-inv-sent,.adminhtml-report-test-customer .col-inv-acc{text-align:right;width:120px}.adminhtml-report-test-index .col-period{white-space:nowrap}.adminhtml-report-test-index .col-inv-sent,.adminhtml-report-test-index .col-inv-acc,.adminhtml-report-test-index .col-inv-disc,.adminhtml-report-test-index .col-inv-acc-rate,.adminhtml-report-test-index .col-inv-disc-rate{text-align:right;width:19%}.test_information .data-table,.invitee_information .data-table,.inviter_information .data-table{width:100%}.test_information .data-table tbody tr th,.invitee_information .data-table tbody tr th,.inviter_information .data-table tbody tr th{font-weight:700}.test_information .data-table tbody tr td,.test_information .data-table tbody tr th,.invitee_information .data-table tbody tr td,.invitee_information .data-table tbody tr th,.inviter_information .data-table tbody tr td,.inviter_information .data-table tbody tr th{background-color:#fff;border:0;padding:9px 10px 10px;color:#666;vertical-align:top}.test_information .data-table tbody tr:nth-child(2n+1) td,.test_information .data-table tbody tr:nth-child(2n+1) th,.invitee_information .data-table tbody tr:nth-child(2n+1) td,.invitee_information .data-table tbody tr:nth-child(2n+1) th,.inviter_information .data-table tbody tr:nth-child(2n+1) td,.inviter_information .data-table tbody tr:nth-child(2n+1) th{background-color:#fbfaf6}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table .col-sort-order{width:80px}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table td,[class^=" adminhtml-test-"] .fieldset-wrapper-content .accordion .config .data-table td{vertical-align:top}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table td select,[class^=" adminhtml-test-"] .fieldset-wrapper-content .accordion .config .data-table td select{display:block;width:100%}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table td .input-radio.global-scope,[class^=" adminhtml-test-"] .fieldset-wrapper-content .accordion .config .data-table td .input-radio.global-scope{margin-top:9px}.sales-order-create-index .ui-dialog .content>.test .field.text .input-text{width:100%}.sales-order-create-index .ui-dialog .content>.test .note .price{font-weight:600}.sales-order-create-index .ui-dialog .content>.test .note .price:before{content:": "}.sales-order-create-index .ui-dialog .content>.test .fixed.amount .label:after{content:": "}.sales-order-create-index .ui-dialog .content>.test .fixed.amount .control{display:inline-block;font-weight:600}.sales-order-create-index .ui-dialog .content>.test .fixed.amount .control .control-value{margin:-2px 0 0;padding:0}.eq-ie9 [class^=" adminhtml-test-"] .custom-options .data-table{word-wrap:normal;table-layout:auto}.rma-items .col-actions a.disabled,.newRma .col-actions a.disabled{cursor:default;opacity:.5}.rma-items .col-actions a.disabled:hover,.newRma .col-actions a.disabled:hover{text-decoration:none}.block.mselect-list .mselect-input{width:100%}.block.mselect-list .mselect-input-container .mselect-save{top:4px}.block.mselect-list .mselect-input-container .mselect-cancel{top:4px}html{font-size:62.5%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;font-size-adjust:100%}body,html{height:100%;min-height:100%}body{color:#676056;font-family:'Open Sans',sans-serif;line-height:1.33;font-weight:400;font-size:1.4rem;background:#f2ebde;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}body>*{-webkit-flex-grow:0;flex-grow:0;-webkit-flex-shrink:0;flex-shrink:0;-webkit-flex-basis:auto;flex-basis:auto}.page-wrapper{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-height:100%;width:100%;max-width:100%;min-width:990px}.page-wrapper>*{-webkit-flex-grow:0;flex-grow:0;-webkit-flex-shrink:0;flex-shrink:0;-webkit-flex-basis:auto;flex-basis:auto}.page-header{text-align:right}.page-header-wrapper{background-color:#31302b}.page-header:after{content:"";display:table;clear:both}.page-header .logo{margin-top:5px;float:left;text-decoration:none;display:inline-block}.page-header .logo:before{content:"";display:inline-block;vertical-align:middle;width:109px;height:35px;background-image:url("../images/logo.svg");background-size:109px 70px;background-repeat:no-repeat}.page-header .logo:after{display:inline-block;vertical-align:middle;margin-left:10px;content:attr(data-edition);font-weight:600;font-size:16px;color:#ef672f;margin-top:-2px}.page-header .logo span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.page-header .logo span.focusable:active,.page-header .logo span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.page-header .logo span.focusable:active,.page-header .logo span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.page-header .dropdown-menu{border:0}.admin-user{display:inline-block;vertical-align:top;position:relative;text-align:left}.admin-user-account{text-decoration:none;display:inline-block;padding:12px 14px;color:#f2ebde}.admin-user-account:after{font-family:"MUI-Icons";content:"\e02c";font-size:13px;line-height:13px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:-3px 0 0}.admin-user-account:link,.admin-user-account:visited{color:#f2ebde}.admin-user-account:focus,.admin-user-account:active,.admin-user-account:hover{color:#f2ebde;text-decoration:none}.active .admin-user-account{background-color:#FFF;color:#676056}.admin-user-menu{padding:15px;white-space:nowrap;margin-top:0}.admin-user-menu li{border:0;padding:0}.admin-user-menu li:hover{background:transparent}.admin-user-menu a{display:block;color:#676056;font-size:13px;font-weight:400;line-height:1.385;padding:3px 12px 3px;text-decoration:none}.admin-user-menu a:focus,.admin-user-menu a:hover{text-decoration:underline}.admin-user-menu a:hover{color:#fff;background:#989287;text-decoration:none}.admin-user-menu a span:before{content:"("}.admin-user-menu a span:after{content:")"}.page-actions.fixed .page-actions-buttons{padding-right:15px}.page-main-actions{background:#e0dace;color:#645d53;padding:15px;margin-left:auto;margin-right:auto;box-sizing:border-box}.page-main-actions:before,.page-main-actions:after{content:"";display:table}.page-main-actions:after{clear:both}.page-main-actions:before,.page-main-actions:after{content:"";display:table}.page-main-actions:after{clear:both}.page-main-actions .page-actions{float:right}.page-main-actions .page-actions .page-actions-buttons{float:right;display:-webkit-flex;display:-ms-flexbox;display:flex;justify-content:flex-end}.page-main-actions .page-actions button,.page-main-actions .page-actions .action-add.mselect-button-add{margin-left:13px}.page-main-actions .page-actions button.primary,.page-main-actions .page-actions .action-add.mselect-button-add.primary{float:right;-ms-flex-order:2;-webkit-order:2;order:2}.page-main-actions .page-actions button.save:not(.primary),.page-main-actions .page-actions .action-add.mselect-button-add.save:not(.primary){float:right;-ms-flex-order:1;-webkit-order:1;order:1}.page-main-actions .page-actions button.back,.page-main-actions .page-actions button.action-back,.page-main-actions .page-actions button.delete,.page-main-actions .page-actions .action-add.mselect-button-add.back,.page-main-actions .page-actions .action-add.mselect-button-add.action-back,.page-main-actions .page-actions .action-add.mselect-button-add.delete{background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400;margin:0 13px}.page-main-actions .page-actions button.back:focus,.page-main-actions .page-actions button.action-back:focus,.page-main-actions .page-actions button.delete:focus,.page-main-actions .page-actions button.back:active,.page-main-actions .page-actions button.action-back:active,.page-main-actions .page-actions button.delete:active,.page-main-actions .page-actions .action-add.mselect-button-add.back:focus,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:focus,.page-main-actions .page-actions .action-add.mselect-button-add.delete:focus,.page-main-actions .page-actions .action-add.mselect-button-add.back:active,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:active,.page-main-actions .page-actions .action-add.mselect-button-add.delete:active{background:none;border:none}.page-main-actions .page-actions button.back:hover,.page-main-actions .page-actions button.action-back:hover,.page-main-actions .page-actions button.delete:hover,.page-main-actions .page-actions .action-add.mselect-button-add.back:hover,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:hover,.page-main-actions .page-actions .action-add.mselect-button-add.delete:hover{background:none;border:none}.page-main-actions .page-actions button.back.disabled,.page-main-actions .page-actions button.action-back.disabled,.page-main-actions .page-actions button.delete.disabled,.page-main-actions .page-actions button.back[disabled],.page-main-actions .page-actions button.action-back[disabled],.page-main-actions .page-actions button.delete[disabled],fieldset[disabled] .page-main-actions .page-actions button.back,fieldset[disabled] .page-main-actions .page-actions button.action-back,fieldset[disabled] .page-main-actions .page-actions button.delete,.page-main-actions .page-actions .action-add.mselect-button-add.back.disabled,.page-main-actions .page-actions .action-add.mselect-button-add.action-back.disabled,.page-main-actions .page-actions .action-add.mselect-button-add.delete.disabled,.page-main-actions .page-actions .action-add.mselect-button-add.back[disabled],.page-main-actions .page-actions .action-add.mselect-button-add.action-back[disabled],.page-main-actions .page-actions .action-add.mselect-button-add.delete[disabled],fieldset[disabled] .page-main-actions .page-actions .action-add.mselect-button-add.back,fieldset[disabled] .page-main-actions .page-actions .action-add.mselect-button-add.action-back,fieldset[disabled] .page-main-actions .page-actions .action-add.mselect-button-add.delete{cursor:not-allowed;pointer-events:none;opacity:.5}.ie .page-main-actions .page-actions button.back,.ie .page-main-actions .page-actions button.action-back,.ie .page-main-actions .page-actions button.delete,.ie .page-main-actions .page-actions .action-add.mselect-button-add.back,.ie .page-main-actions .page-actions .action-add.mselect-button-add.action-back,.ie .page-main-actions .page-actions .action-add.mselect-button-add.delete{margin-top:6px}.page-main-actions .page-actions button.delete,.page-main-actions .page-actions .action-add.mselect-button-add.delete{color:#e22626;float:left;-ms-flex-order:-1;-webkit-order:-1;order:-1}.page-main-actions .page-actions button.back,.page-main-actions .page-actions button.action-back,.page-main-actions .page-actions .action-add.mselect-button-add.back,.page-main-actions .page-actions .action-add.mselect-button-add.action-back{float:left;-ms-flex-order:-1;-webkit-order:-1;order:-1;display:inline-block;text-decoration:none}.page-main-actions .page-actions button.back:before,.page-main-actions .page-actions button.action-back:before,.page-main-actions .page-actions .action-add.mselect-button-add.back:before,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:before{font-family:'icons-blank-theme';content:'\e625';font-size:inherit;line-height:normal;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:0 2px 0 0}.page-main-actions .page-actions .actions-split{margin-left:13px;float:right;-ms-flex-order:2;-webkit-order:2;order:2}.page-main-actions .page-actions .actions-split button.primary,.page-main-actions .page-actions .actions-split .action-add.mselect-button-add.primary{float:left}.page-main-actions .page-actions .actions-split .dropdown-menu{text-align:left}.page-main-actions .page-actions .actions-split .dropdown-menu .item{display:block}.page-main-actions .page-actions.fixed{position:fixed;top:0;left:0;right:0;z-index:10;padding:0;background:-webkit-linear-gradient(top,#f5f2ed 0%,#f5f2ed 56%,rgba(245,242,237,0) 100%);background:-ms-linear-gradient(top,#f5f2ed 0%,#f5f2ed 56%,rgba(245,242,237,0) 100%);background:linear-gradient(to bottom,#f5f2ed 0%,#f5f2ed 56%,rgba(245,242,237,0) 100%);background:#e0dace}.page-main-actions .page-actions.fixed .page-actions-inner{position:relative;padding-top:15px;padding-bottom:15px;min-height:36px;text-align:right;box-sizing:border-box}.page-main-actions .page-actions.fixed .page-actions-inner:before,.page-main-actions .page-actions.fixed .page-actions-inner:after{content:"";display:table}.page-main-actions .page-actions.fixed .page-actions-inner:after{clear:both}.page-main-actions .page-actions.fixed .page-actions-inner:before,.page-main-actions .page-actions.fixed .page-actions-inner:after{content:"";display:table}.page-main-actions .page-actions.fixed .page-actions-inner:after{clear:both}.page-main-actions .page-actions.fixed .page-actions-inner:before{text-align:left;content:attr(data-title);float:left;font-size:20px;max-width:50%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-ie10 .page-main-actions .page-actions.fixed .page-actions-inner{background:#f5f2ed}.page-main-actions .store-switcher{margin-top:5px}.store-switcher{display:inline-block;font-size:13px}.store-switcher .label{margin-right:5px}.store-switcher .actions.dropdown{display:inline-block;position:relative}.store-switcher .actions.dropdown:before,.store-switcher .actions.dropdown:after{content:"";display:table}.store-switcher .actions.dropdown:after{clear:both}.store-switcher .actions.dropdown:before,.store-switcher .actions.dropdown:after{content:"";display:table}.store-switcher .actions.dropdown:after{clear:both}.store-switcher .actions.dropdown .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}.store-switcher .actions.dropdown .action.toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:20px;color:#645d53;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.store-switcher .actions.dropdown .action.toggle:hover:after{color:#645d53}.store-switcher .actions.dropdown .action.toggle:active:after{color:#645d53}.store-switcher .actions.dropdown .action.toggle.active{display:inline-block;text-decoration:none}.store-switcher .actions.dropdown .action.toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:20px;color:#645d53;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.store-switcher .actions.dropdown .action.toggle.active:hover:after{color:#645d53}.store-switcher .actions.dropdown .action.toggle.active:active:after{color:#645d53}.store-switcher .actions.dropdown .dropdown-menu{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px #ada89e solid;position:absolute;z-index:100;top:100%;min-width:195px;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}.store-switcher .actions.dropdown .dropdown-menu li{margin:0;padding:0}.store-switcher .actions.dropdown .dropdown-menu li:hover{background:transparent;cursor:pointer}.store-switcher .actions.dropdown.active{overflow:visible}.store-switcher .actions.dropdown.active .dropdown-menu{display:block}.store-switcher .actions.dropdown .action.toggle{background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400;color:#026294;line-height:normal;margin-top:2px;vertical-align:middle}.store-switcher .actions.dropdown .action.toggle:focus,.store-switcher .actions.dropdown .action.toggle:active{background:none;border:none}.store-switcher .actions.dropdown .action.toggle:hover{background:none;border:none}.store-switcher .actions.dropdown .action.toggle.disabled,.store-switcher .actions.dropdown .action.toggle[disabled],fieldset[disabled] .store-switcher .actions.dropdown .action.toggle{cursor:not-allowed;pointer-events:none;opacity:.5}.store-switcher .actions.dropdown ul.dropdown-menu{margin-top:4px;padding-top:5px;left:0}.store-switcher .actions.dropdown ul.dropdown-menu li{border:0;cursor:default}.store-switcher .actions.dropdown ul.dropdown-menu li:hover{cursor:default}.store-switcher .actions.dropdown ul.dropdown-menu li a,.store-switcher .actions.dropdown ul.dropdown-menu li span{padding:5px 13px;display:block;color:#645d53}.store-switcher .actions.dropdown ul.dropdown-menu li a{text-decoration:none}.store-switcher .actions.dropdown ul.dropdown-menu li a:hover{background:#edf9fb}.store-switcher .actions.dropdown ul.dropdown-menu li span{color:#ababab;cursor:default}.store-switcher .actions.dropdown ul.dropdown-menu li.current span{color:#645d53;background:#eee}.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store a,.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store span{padding-left:26px}.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store-view a,.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store-view span{padding-left:39px}.store-switcher .actions.dropdown ul.dropdown-menu .dropdown-toolbar{border-top:1px #ededed solid;margin-top:10px}.store-switcher .actions.dropdown ul.dropdown-menu .dropdown-toolbar a{display:inline-block;text-decoration:none;display:block}.store-switcher .actions.dropdown ul.dropdown-menu .dropdown-toolbar a:before{font-family:'icons-blank-theme';content:'\e606';font-size:20px;line-height:normal;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:text-top;text-align:center;margin:0 3px 0 -4px}.tooltip{display:inline-block;margin-left:5px}.tooltip .help span,.tooltip .help a{width:16px;height:16px;text-align:center;background:rgba(194,186,169,.5);cursor:pointer;border-radius:10px;vertical-align:middle;display:inline-block;text-decoration:none}.tooltip .help span:hover,.tooltip .help a:hover{background:#c2baa9}.tooltip .help span>span,.tooltip .help a>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.tooltip .help span>span.focusable:active,.tooltip .help a>span.focusable:active,.tooltip .help span>span.focusable:focus,.tooltip .help a>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.tooltip .help span>span.focusable:active,.tooltip .help a>span.focusable:active,.tooltip .help span>span.focusable:focus,.tooltip .help a>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.tooltip .help span:before,.tooltip .help a:before{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;content:'?';font-size:13px;line-height:16px;color:#5a534a;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center}.tooltip .help span:before,.tooltip .help a:before{font-weight:700}.tooltip .tooltip-content{display:none;position:absolute;max-width:200px;margin-top:10px;margin-left:-19px;padding:4px 8px;border-radius:3px;background:#000;background:rgba(49,48,43,.8);color:#fff;text-shadow:none;z-index:20}.tooltip .tooltip-content:before{content:'';position:absolute;width:0;height:0;top:-5px;left:20px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000;opacity:.8}.tooltip .tooltip-content.loading{position:absolute}.tooltip .tooltip-content.loading:before{border-bottom-color:rgba(0,0,0,.3)}.tooltip:hover>.tooltip-content{display:block}button,.action-add.mselect-button-add{border-radius:2px;background-image:none;background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:0;vertical-align:middle}button:focus,button:active,.action-add.mselect-button-add:focus,.action-add.mselect-button-add:active{background:#cac3b4;border:1px solid #989287}button:hover,.action-add.mselect-button-add:hover{background:#cac3b4}button.disabled,button[disabled],fieldset[disabled] button,.action-add.mselect-button-add.disabled,.action-add.mselect-button-add[disabled],fieldset[disabled] .action-add.mselect-button-add{cursor:default;pointer-events:none;opacity:.5}button.primary,.action-add.mselect-button-add.primary{background-image:none;background:#007dbd;padding:6px 13px;color:#fff;border:1px solid #0a6c9f;cursor:pointer;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;box-sizing:border-box;vertical-align:middle}button.primary:focus,button.primary:active,.action-add.mselect-button-add.primary:focus,.action-add.mselect-button-add.primary:active{background:#026294;border:1px solid #004c74;color:#fff}button.primary:hover,.action-add.mselect-button-add.primary:hover{background:#026294;border:1px solid #026294}button.primary.disabled,button.primary[disabled],fieldset[disabled] button.primary,.action-add.mselect-button-add.primary.disabled,.action-add.mselect-button-add.primary[disabled],fieldset[disabled] .action-add.mselect-button-add.primary{cursor:default;pointer-events:none;opacity:.5}.actions-split{display:inline-block;position:relative;vertical-align:middle}.actions-split button,.actions-split .action-add.mselect-button-add{margin-left:0 !important}.actions-split:before,.actions-split:after{content:"";display:table}.actions-split:after{clear:both}.actions-split:before,.actions-split:after{content:"";display:table}.actions-split:after{clear:both}.actions-split .action-default{float:left;margin:0}.actions-split .action-toggle{float:right;margin:0}.actions-split button.action-default,.actions-split .action-add.mselect-button-add.action-default{border-top-right-radius:0;border-bottom-right-radius:0}.actions-split button+.action-toggle,.actions-split .action-add.mselect-button-add+.action-toggle{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.actions-split .action-toggle{padding:6px 5px;display:inline-block;text-decoration:none}.actions-split .action-toggle>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.actions-split .action-toggle>span.focusable:active,.actions-split .action-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle>span.focusable:active,.actions-split .action-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.actions-split .action-toggle:hover:after{color:inherit}.actions-split .action-toggle:active:after{color:inherit}.actions-split .action-toggle.active{display:inline-block;text-decoration:none}.actions-split .action-toggle.active>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.actions-split .action-toggle.active>span.focusable:active,.actions-split .action-toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle.active>span.focusable:active,.actions-split .action-toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.actions-split .action-toggle.active:hover:after{color:inherit}.actions-split .action-toggle.active:active:after{color:inherit}.actions-split .dropdown-menu{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px solid #bbb;position:absolute;z-index:100;top:100%;min-width:175px;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}.actions-split .dropdown-menu li{margin:0;padding:3px 5px}.actions-split .dropdown-menu li:hover{background:#e8e8e8;cursor:pointer}.actions-split .dropdown-menu:before,.actions-split .dropdown-menu:after{content:"";position:absolute;display:block;width:0;height:0;border-bottom-style:solid}.actions-split .dropdown-menu:before{z-index:99;border:solid 6px;border-color:transparent transparent #fff transparent}.actions-split .dropdown-menu:after{z-index:98;border:solid 7px;border-color:transparent transparent #bbb transparent}.actions-split .dropdown-menu:before{top:-12px;right:10px}.actions-split .dropdown-menu:after{top:-14px;right:9px}.actions-split.active{overflow:visible}.actions-split.active .dropdown-menu{display:block}.actions-split .action-toggle:after{height:13px}.page-content:after{content:"";display:table;clear:both}.page-wrapper>.page-content{margin-bottom:20px}.page-footer{padding:15px 0}.page-footer-wrapper{background-color:#e0dacf;margin-top:auto}.page-footer:after{content:"";display:table;clear:both}.footer-legal{float:right;width:550px}.footer-legal .link-report,.footer-legal .magento-version,.footer-legal .copyright{font-size:13px}.footer-legal:before{content:"";display:inline-block;vertical-align:middle;position:absolute;z-index:1;margin-top:2px;margin-left:-35px;width:30px;height:35px;background-size:109px 70px;background:url("../images/logo.svg") no-repeat 0 -21px}.icon-error{margin-left:15px;color:#c00815;font-size:11px}.icon-error:before{font-family:'MUI-Icons';content:"\e086";font-size:13px;line-height:13px;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:-1px 5px 0 0}.ui-widget-overlay{position:fixed}.control .nested{padding:0}.control *:first-child{margin-bottom:0}.field-tooltip{display:inline-block;vertical-align:top;margin-top:5px;position:relative;z-index:1;width:0;overflow:visible}.field-choice .field-tooltip{margin-top:10px}.field-tooltip:hover{z-index:99}.field-tooltip-action{position:relative;z-index:2;margin-left:18px;width:22px;height:22px;display:inline-block;cursor:pointer}.field-tooltip-action:before{content:"?";font-weight:500;font-size:18px;display:inline-block;overflow:hidden;height:22px;border-radius:11px;line-height:22px;width:22px;text-align:center;color:#fff;background-color:#514943}.field-tooltip-action span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.field-tooltip-action span.focusable:active,.field-tooltip-action span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.field-tooltip-action span.focusable:active,.field-tooltip-action span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.control-text:focus+.field-tooltip-content,.field-tooltip:hover .field-tooltip-content{display:block}.field-tooltip-content{display:none;position:absolute;z-index:1;width:320px;background:#fff8d7;padding:15px 25px;right:-66px;border:1px solid #adadad;border-radius:1px;bottom:42px;box-shadow:0 2px 8px 0 rgba(0,0,0,.3)}.field-tooltip-content:after,.field-tooltip-content:before{content:"";display:block;width:0;height:0;border:16px solid transparent;border-top-color:#adadad;position:absolute;right:20px;top:100%;z-index:3}.field-tooltip-content:after{border-top-color:#fff8d7;margin-top:-1px;z-index:4}.form__field.field-error .control [class*="control-"]{border-color:#e22626}.form__field.field-error .control [class*="control-"]:before{border-color:#e22626}.form__field .mage-error{border:1px solid #e22626;display:block;margin:2px 0 0;padding:6px 10px 10px;background:#fff8d6;color:#555;font-size:12px;font-weight:500;box-sizing:border-box;max-width:380px}.no-flexbox.no-flexboxlegacy .form__field .control-addon+.mage-error{display:inline-block;width:100%}.form__field{position:relative;z-index:1}.form__field:hover{z-index:2}.control .form__field{position:static}.form__field[data-config-scope]:before{content:attr(data-config-scope);display:inline-block;position:absolute;color:gray;right:0;top:6px}.control .form__field[data-config-scope]:nth-child(n+2):before{content:""}.form__field.field-disabled>.label{color:#999}.form__field.field-disabled.field .control [class*="control-"][disabled]{background-color:#e9e9e9;opacity:.5;color:#303030;border-color:#adadad}.control-fields .label~.control{width:100%}.form__field{border:0;padding:0}.form__field .note{color:#303030;padding:0;margin:10px 0 0;max-width:380px}.form__field .note:before{display:none}.form__field.form__field{margin-bottom:0}.form__field.form__field+.form__field.form__field{margin-top:15px}.form__field.form__field:not(.choice)~.choice{margin-left:20px;margin-top:5px}.form__field.form__field.choice~.choice{margin-top:9px}.form__field.form__field~.choice:last-child{margin-bottom:8px}.fieldset>.form__field{margin-bottom:30px}.form__field .label{color:#303030}.form__field:not(.choice)>.label{font-size:14px;font-weight:600;width:30%;padding-right:30px;padding-top:0;line-height:33px;white-space:nowrap}.form__field:not(.choice)>.label:before{content:".";visibility:hidden;width:0;margin-left:-7px;overflow:hidden}.form__field:not(.choice)>.label span{white-space:normal;display:inline-block;vertical-align:middle;line-height:1.2}.form__field.required>.label:after{content:"";margin-left:0}.form__field.required>.label span:after{content:"*";color:#eb5202;display:inline;font-weight:500;font-size:16px;margin-top:2px;position:absolute;z-index:1;margin-left:10px}.form__field .control-file{margin-top:6px}.form__field .control-select{line-height:33px}.form__field .control-select:not([multiple]),.form__field .control-text{height:33px;max-width:380px}.form__field .control-addon{max-width:380px}.form__field .control-textarea,.form__field .control-select,.form__field .control-text{border:1px solid #adadad;border-radius:1px;padding:0 10px;color:#303030;background-color:#fff;font-weight:500;font-size:15px;min-width:11em}.form__field .control-textarea:focus,.form__field .control-select:focus,.form__field .control-text:focus{outline:0;border-color:#007bdb;box-shadow:none}.form__field .control-text{line-height:auto}.form__field .control-textarea{padding-top:6px;padding-bottom:6px;line-height:1.18em}.form__field .control-select[multiple],.form__field .control-textarea{width:100%;height:calc(6*1.2em + 14px)}.form__field .control-value{display:inline-block;padding:6px 10px}.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:active,.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:active,.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field .control-select{padding:0}.form__field .control-select option{box-sizing:border-box;padding:4px 10px;display:block}.form__field .control-select optgroup{font-weight:600;display:block;padding:4px 10px;line-height:33px;list-style:inside;font-style:normal}.form__field .control-range>.form__field:nth-child(2):before{content:"\2014";content:":";display:inline-block;margin-left:-25px;float:left;width:20px;line-height:33px;text-align:center}.form__field.choice{position:relative;z-index:1;padding-top:8px;padding-left:26px;padding-right:0}.form__field.choice .label{font-weight:500;padding:0;display:inline;float:none;line-height:18px}.form__field.choice input{position:absolute;top:8px;margin-top:3px !important}.form__field.choice input[disabled]+.label{opacity:.5;cursor:not-allowed}.control>.form__field.choice{max-width:380px}.control>.form__field.choice:nth-child(1):nth-last-child(2),.control>.form__field.choice:nth-child(2):nth-last-child(1){display:inline-block}.control>.form__field.choice:nth-child(1):nth-last-child(2)+.choice,.control>.form__field.choice:nth-child(2):nth-last-child(1)+.choice{margin-left:41px;margin-top:0}.control>.form__field.choice:nth-child(1):nth-last-child(2)+.choice:before,.control>.form__field.choice:nth-child(2):nth-last-child(1)+.choice:before{content:"";position:absolute;display:inline-block;height:20px;top:8px;left:-20px;width:1px;background:#ccc}.form__field.choice .label{cursor:pointer}.form__field.choice .label:before{content:"";position:absolute;z-index:1;border:1px solid #adadad;width:14px;height:14px;top:10px;left:0;border-radius:2px;background:url("../Magento_Ui/images/choice_bkg.png") no-repeat -100% -100%}.form__field.choice input:focus+.label:before{outline:0;border-color:#007bdb}.form__field.choice .control-radio+.label:before{border-radius:8px}.form__field.choice .control-radio:checked+.label:before{background-position:-26px -1px}.form__field.choice .control-checkbox:checked+.label:before{background-position:-1px -1px}.form__field.choice input{opacity:0}.fieldset>.form__field.choice{margin-left:30%}.form__field .control-after,.form__field .control-before{border:0;color:#858585;font-weight:300;font-size:15px;line-height:33px;display:inline-block;height:33px;box-sizing:border-box;padding:0 3px}.no-flexbox.no-flexboxlegacy .form__field .control-before,.no-flexbox.no-flexboxlegacy .form__field .control-addon{float:left;white-space:nowrap}.form__field .control-addon{display:inline-flex;max-width:380px;width:100%;flex-flow:row nowrap;position:relative;z-index:1}.form__field .control-addon>*{position:relative;z-index:1}.form__field .control-addon .control-text[disabled][type],.form__field .control-addon .control-select[disabled][type],.form__field .control-addon .control-select,.form__field .control-addon .control-text{background:transparent !important;border:0;width:auto;vertical-align:top;order:1;flex:1}.form__field .control-addon .control-text[disabled][type]:focus,.form__field .control-addon .control-select[disabled][type]:focus,.form__field .control-addon .control-select:focus,.form__field .control-addon .control-text:focus{box-shadow:none}.form__field .control-addon .control-text[disabled][type]:focus+label:before,.form__field .control-addon .control-select[disabled][type]:focus+label:before,.form__field .control-addon .control-select:focus+label:before,.form__field .control-addon .control-text:focus+label:before{outline:0;border-color:#007bdb}.form__field .control-addon .control-text[disabled][type]+label,.form__field .control-addon .control-select[disabled][type]+label,.form__field .control-addon .control-select+label,.form__field .control-addon .control-text+label{padding-left:10px;position:static !important;z-index:0}.form__field .control-addon .control-text[disabled][type]+label>*,.form__field .control-addon .control-select[disabled][type]+label>*,.form__field .control-addon .control-select+label>*,.form__field .control-addon .control-text+label>*{vertical-align:top;position:relative;z-index:2}.form__field .control-addon .control-text[disabled][type]+label:before,.form__field .control-addon .control-select[disabled][type]+label:before,.form__field .control-addon .control-select+label:before,.form__field .control-addon .control-text+label:before{box-sizing:border-box;border-radius:1px;border:1px solid #adadad;content:"";display:block;position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;background:#fff}.form__field .control-addon .control-text[disabled][type][disabled]+label:before,.form__field .control-addon .control-select[disabled][type][disabled]+label:before,.form__field .control-addon .control-select[disabled]+label:before,.form__field .control-addon .control-text[disabled]+label:before{opacity:.5;background:#e9e9e9}.form__field .control-after{order:3}.form__field .control-after:last-child{padding-right:10px}.form__field .control-before{order:0}.form__field .control-some{display:flex}.form__field [class*="control-grouped"]{display:table;width:100%;table-layout:fixed;box-sizing:border-box}.form__field [class*="control-grouped"]>.form__field{display:table-cell;width:50%;vertical-align:top}.form__field [class*="control-grouped"]>.form__field>.control{width:100%;float:none}.form__field [class*="control-grouped"]>.form__field:nth-child(n+2){padding-left:20px}.form__field [class*="control-grouped"]>.form__field:nth-child(n+2):not(.choice) .label{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.form__field [class*="control-grouped"]>.form__field:nth-child(n+2):not(.choice) .label.focusable:active,.form__field [class*="control-grouped"]>.form__field:nth-child(n+2):not(.choice) .label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field [class*="control-grouped"]>.form__field:nth-child(n+2):not(.choice) .label.focusable:active,.form__field [class*="control-grouped"]>.form__field:nth-child(n+2):not(.choice) .label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field [required]{box-shadow:none}fieldset.form__field{position:relative}fieldset.form__field [class*="control-grouped"]>.form__field:first-child>.label,fieldset.form__field .control-fields>.form__field:first-child>.label{position:absolute;left:0;top:0;opacity:0;cursor:pointer;width:30%}.control-text+.ui-datepicker-trigger{background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;line-height:inherit;font-weight:400;text-decoration:none;margin-left:-40px;display:inline-block}.control-text+.ui-datepicker-trigger img{display:none}.control-text+.ui-datepicker-trigger:focus,.control-text+.ui-datepicker-trigger:active{background:none;border:none}.control-text+.ui-datepicker-trigger:hover{background:none;border:none}.control-text+.ui-datepicker-trigger.disabled,.control-text+.ui-datepicker-trigger[disabled],fieldset[disabled] .control-text+.ui-datepicker-trigger{cursor:not-allowed;pointer-events:none;opacity:.5}.control-text+.ui-datepicker-trigger>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.control-text+.ui-datepicker-trigger>span.focusable:active,.control-text+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.control-text+.ui-datepicker-trigger>span.focusable:active,.control-text+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.control-text+.ui-datepicker-trigger:after{font-family:'icons-blank-theme';content:'\e612';font-size:38px;line-height:33px;color:#514943;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}[class*="tab-nav-item"]:not(ul):active,[class*="tab-nav-item"]:not(ul):focus{box-shadow:none;outline:none}.customer-index-edit .col-2-left-layout,.customer-index-edit .col-1-layout{background:#fff}.customer-index-edit{background:#fff}.customer-index-edit .col-2-left-layout{background:#fff}.customer-index-edit .main-col{padding-left:40px}.customer-index-edit .page-main-actions{background:transparent}.tab-nav.block{margin-bottom:40px}.tab-nav.block:first-child{margin-top:16px}.tab-nav.block .block-title{padding:7px 20px}.tab-nav-items{padding:0;border:1px solid #d3d3d3;box-shadow:0 0 4px rgba(50,50,50,.35);margin:0 0 40px;background:#f7f7f7}.tab-nav-item{padding:0;list-style-type:none;border-bottom:1px solid #e0e0e0;position:relative;margin:0 15px;z-index:1}.tab-nav-item:last-child{border-bottom:0}.tab-nav-item.ui-state-active{z-index:2;background:#FFF;padding:1px 14px;border:2px solid #eb5202;margin:-1px}.tab-nav-item.ui-state-active .tab-nav-item-link{padding:13px 15px 13px;color:#eb5202}.tab-nav-item.ui-tabs-loading{position:relative;z-index:1}.tab-nav-item.ui-tabs-loading:before{content:"";display:block;position:absolute;z-index:2;background:url("../images/loader-2.gif") no-repeat 50% 50%;background-size:120px;width:20px;height:20px;top:13px;left:-10px}.tab-nav-item.ui-tabs-loading.ui-state-active:before{top:12px;left:4px}.tab-nav-item-link{display:block;padding:15px;color:#666;line-height:1}.tab-nav-item-link:focus,.tab-nav-item-link:active,.tab-nav-item-link:hover{outline:0;color:#eb5202;text-decoration:none}.ui-state-active .tab-nav-item-link{color:#666;font-weight:600}.tab-nav-item-link.changed{font-style:italic}.listing-tiles{overflow:hidden;margin-top:-10px;margin-left:-10px}.listing-tiles .listing-tile{background-color:#f2ebde;display:block;width:238px;height:200px;float:left;border:1px solid #676056;margin-top:10px;margin-left:10px;border-radius:4px;text-align:center}.listing-tiles .listing-tile.disabled{border-color:red}.listing-tiles .listing-tile.enabled{border-color:green}.listing .disabled{color:red}.listing .enabled{color:green}.pager{text-align:left;padding-bottom:10px}.pager:before,.pager:after{content:"";display:table}.pager:after{clear:both}.pager:before,.pager:after{content:"";display:table}.pager:after{clear:both}.pager [data-part=left]{display:inline-block;width:45%;float:left;text-align:left}.pager [data-part=right]{display:inline-block;width:45%;text-align:right;float:right;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.pager .action-next{cursor:pointer}.pager .action-previous{cursor:pointer}.pager{text-align:left}.pager [data-part=left]{display:inline-block;width:45%;text-align:left}.pager [data-part=right]{display:inline-block;width:45%;text-align:right;float:right}.grid .col-title{min-width:90px;text-align:center}.grid-actions [data-part=search]{display:inline-block;margin:0 30px}.grid-actions [data-part=search] input[type=text]{vertical-align:bottom;width:460px}.grid .actions-split .dropdown-menu{right:auto;left:auto;text-align:left;color:#676056;font-weight:400}.grid .actions-split .dropdown-menu:after{right:auto;left:9px}.grid .actions-split .dropdown-menu:before{right:auto;left:10px}.grid .grid-actions{padding:10px 0}.grid .hor-scroll{padding-top:10px}.grid .select-box{display:inline-block;vertical-align:top;margin:-12px -10px -7px;padding:12px 10px 7px;width:100%}.filters-toggle{background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400}.filters-toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:30px;line-height:15px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.filters-toggle:hover:after{color:inherit}.filters-toggle:active:after{color:inherit}.filters-toggle:focus,.filters-toggle:active{background:#cac3b4;border:1px solid #989287}.filters-toggle:hover{background:#cac3b4}.filters-toggle.disabled,.filters-toggle[disabled],fieldset[disabled] .filters-toggle{cursor:default;pointer-events:none;opacity:.5}.filters-toggle:focus,.filters-toggle:active{background:none;border:none}.filters-toggle:hover{background:none;border:none}.filters-toggle.disabled,.filters-toggle[disabled],fieldset[disabled] .filters-toggle{cursor:not-allowed;pointer-events:none;opacity:.5}.filters-toggle:after{margin-top:2px;margin-left:-3px}.filters-toggle.active:after{content:'\e618'}.filters-current{padding:10px 0;display:none}.filters-current.active{display:block}.filters-items{margin:0;padding:0;list-style:none none;display:inline}.filters-item{display:inline-block;margin:0 5px 5px 0;padding:2px 2px 2px 4px;border-radius:3px;background:#f7f3eb}.filters-item .item-label{font-weight:600}.filters-item .item-label:after{content:": "}.filters-item .action-remove{background-image:none;background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;text-decoration:none;padding:0}.filters-item .action-remove>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.filters-item .action-remove>span.focusable:active,.filters-item .action-remove>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-item .action-remove>span.focusable:active,.filters-item .action-remove>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-item .action-remove:before{font-family:'icons-blank-theme';content:'\e616';font-size:16px;line-height:16px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.filters-item .action-remove:hover:before{color:inherit}.filters-item .action-remove:active:before{color:inherit}.filters-item .action-remove:focus,.filters-item .action-remove:active{background:#cac3b4;border:1px solid #989287}.filters-item .action-remove:hover{background:#cac3b4}.filters-item .action-remove.disabled,.filters-item .action-remove[disabled],fieldset[disabled] .filters-item .action-remove{cursor:default;pointer-events:none;opacity:.5}.filters-form{position:relative;z-index:1;margin:14px 0;background:#fff;border:1px solid #bbb;box-shadow:0 3px 3px rgba(0,0,0,.15)}.filters-form .action-close{position:absolute;top:3px;right:7px;background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400}.filters-form .action-close>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.filters-form .action-close>span.focusable:active,.filters-form .action-close>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-form .action-close>span.focusable:active,.filters-form .action-close>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-form .action-close:before{font-family:'icons-blank-theme';content:'\e616';font-size:42px;line-height:42px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.filters-form .action-close:hover:before{color:inherit}.filters-form .action-close:active:before{color:inherit}.filters-form .action-close:focus,.filters-form .action-close:active{background:#cac3b4;border:1px solid #989287}.filters-form .action-close:hover{background:#cac3b4}.filters-form .action-close.disabled,.filters-form .action-close[disabled],fieldset[disabled] .filters-form .action-close{cursor:default;pointer-events:none;opacity:.5}.filters-form .action-close:focus,.filters-form .action-close:active{background:none;border:none}.filters-form .action-close:hover{background:none;border:none}.filters-form .action-close.disabled,.filters-form .action-close[disabled],fieldset[disabled] .filters-form .action-close{cursor:not-allowed;pointer-events:none;opacity:.5}.filters-actions{margin:18px;text-align:right}.filters-fieldset{padding-bottom:0}.filters-fieldset .field{border:0;margin:0 0 20px;box-sizing:border-box;display:inline-block;padding:0 12px 0 0;width:33.33333333%;vertical-align:top}.filters-fieldset .field:before,.filters-fieldset .field:after{content:"";display:table}.filters-fieldset .field:after{clear:both}.filters-fieldset .field:before,.filters-fieldset .field:after{content:"";display:table}.filters-fieldset .field:after{clear:both}.filters-fieldset .field.choice:before,.filters-fieldset .field.no-label:before{box-sizing:border-box;content:" ";height:1px;float:left;padding:6px 15px 0 0;width:35%}.filters-fieldset .field .description{box-sizing:border-box;float:left;padding:6px 15px 0 0;text-align:right;width:35%}.filters-fieldset .field:not(.choice)>.label{box-sizing:border-box;float:left;padding:6px 15px 0 0;text-align:right;width:35%}.filters-fieldset .field:not(.choice)>.control{float:left;width:65%}.filters-fieldset .field:last-child{margin-bottom:0}.filters-fieldset .field+.fieldset{clear:both}.filters-fieldset .field>.label{font-weight:700}.filters-fieldset .field>.label+br{display:none}.filters-fieldset .field .choice input{vertical-align:top}.filters-fieldset .field .fields.group:before,.filters-fieldset .field .fields.group:after{content:"";display:table}.filters-fieldset .field .fields.group:after{clear:both}.filters-fieldset .field .fields.group:before,.filters-fieldset .field .fields.group:after{content:"";display:table}.filters-fieldset .field .fields.group:after{clear:both}.filters-fieldset .field .fields.group .field{box-sizing:border-box;float:left}.filters-fieldset .field .fields.group.group-2 .field{width:50% !important}.filters-fieldset .field .fields.group.group-3 .field{width:33.3% !important}.filters-fieldset .field .fields.group.group-4 .field{width:25% !important}.filters-fieldset .field .fields.group.group-5 .field{width:20% !important}.filters-fieldset .field .addon{display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-flex-wrap:nowrap;flex-wrap:nowrap;padding:0;width:100%}.filters-fieldset .field .addon textarea,.filters-fieldset .field .addon select,.filters-fieldset .field .addon input{-ms-flex-order:2;-webkit-order:2;order:2;-webkit-flex-basis:100%;flex-basis:100%;box-shadow:none;display:inline-block;margin:0;width:auto}.filters-fieldset .field .addon .addbefore,.filters-fieldset .field .addon .addafter{-ms-flex-order:3;-webkit-order:3;order:3;display:inline-block;box-sizing:border-box;background:#fff;border:1px solid #c2c2c2;border-radius:1px;height:32px;width:100%;padding:0 9px;font-size:14px;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.428571429;background-clip:padding-box;vertical-align:baseline;width:auto;white-space:nowrap;vertical-align:middle}.filters-fieldset .field .addon .addbefore:disabled,.filters-fieldset .field .addon .addafter:disabled{opacity:.5}.filters-fieldset .field .addon .addbefore::-moz-placeholder,.filters-fieldset .field .addon .addafter::-moz-placeholder{color:#c2c2c2}.filters-fieldset .field .addon .addbefore::-webkit-input-placeholder,.filters-fieldset .field .addon .addafter::-webkit-input-placeholder{color:#c2c2c2}.filters-fieldset .field .addon .addbefore:-ms-input-placeholder,.filters-fieldset .field .addon .addafter:-ms-input-placeholder{color:#c2c2c2}.filters-fieldset .field .addon .addbefore{float:left;-ms-flex-order:1;-webkit-order:1;order:1}.filters-fieldset .field .additional{margin-top:10px}.filters-fieldset .field.required>.label:after{content:'*';font-size:1.2rem;color:#e02b27;margin:0 0 0 5px}.filters-fieldset .field .note{font-size:1.2rem;margin:3px 0 0;padding:0;display:inline-block;text-decoration:none}.filters-fieldset .field .note:before{font-family:'icons-blank-theme';content:'\e618';font-size:24px;line-height:12px;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.filters-fieldset .field .label{color:#676056;font-size:13px;font-weight:600;margin:0}.filters .field-date .group .hasDatepicker{width:100%;padding-right:30px}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger{background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;line-height:inherit;font-weight:400;text-decoration:none;margin-left:-33px;display:inline-block;width:30px}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger img{display:none}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:focus,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:active{background:none;border:none}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:hover{background:none;border:none}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger.disabled,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger[disabled],fieldset[disabled] .filters .field-date .group .hasDatepicker+.ui-datepicker-trigger{cursor:not-allowed;pointer-events:none;opacity:.5}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:active,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:active,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:after{font-family:'icons-blank-theme';content:'\e612';font-size:35px;line-height:30px;color:#514943;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.filters .field-range .group .field{margin-bottom:0}.filters .field-range .group .control{width:100%;box-sizing:border-box;padding-right:0;position:relative;z-index:1}.mass-select{position:relative;margin:-6px -10px;padding:6px 2px 6px 10px;z-index:1;white-space:nowrap}.mass-select.active{background:rgba(0,0,0,.2)}.mass-select-toggle{background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;background-image:none;background:none;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400}.mass-select-toggle>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.mass-select-toggle>span.focusable:active,.mass-select-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.mass-select-toggle>span.focusable:active,.mass-select-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.mass-select-toggle:before{font-family:'icons-blank-theme';content:'\e607';font-size:30px;line-height:15px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.mass-select-toggle:hover:before{color:inherit}.mass-select-toggle:active:before{color:inherit}.mass-select-toggle:focus,.mass-select-toggle:active{background:#cac3b4;border:1px solid #989287}.mass-select-toggle:hover{background:#cac3b4}.mass-select-toggle.disabled,.mass-select-toggle[disabled],fieldset[disabled] .mass-select-toggle{cursor:default;pointer-events:none;opacity:.5}.mass-select-toggle:focus,.mass-select-toggle:active{background:none;border:none}.mass-select-toggle:hover{background:none;border:none}.mass-select-toggle.disabled,.mass-select-toggle[disabled],fieldset[disabled] .mass-select-toggle{cursor:not-allowed;pointer-events:none;opacity:.5}.mass-select-toggle:before{margin-top:-2px;text-indent:-5px;color:#fff}.mass-select-toggle:hover:before{color:#fff}.mass-select-toggle:active:before,.mass-select-toggle.active:before{content:'\e618'}.mass-select-field{display:inline}.mass-select-menu{display:none;position:absolute;top:100%;left:0;text-align:left;margin:0;padding:0;list-style:none none;background:#fff;border:1px solid #bbb;min-width:175px;box-shadow:0 3px 3px rgba(0,0,0,.15)}.mass-select-menu li{margin:0;padding:4px 15px;border-bottom:1px solid #e5e5e5}.mass-select-menu li:hover{background:#e8e8e8;cursor:pointer}.mass-select-menu span{font-weight:400;font-size:13px;color:#645d53}.mass-select-menu.active{display:block}.grid-loading-mask{position:absolute;left:0;top:0;right:0;bottom:0;background:rgba(255,255,255,.5);z-index:100}.grid-loading-mask .grid-loader{position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;width:218px;height:149px;background:url('../images/loader-2.gif') 50% 50% no-repeat}.addon input{border-width:1px 0 1px 1px}.addon input~.addafter strong{display:inline-block;background:#fff;line-height:24px;margin:0 3px 0 -2px;padding-left:4px;padding-right:4px;position:relative;font-size:12px;top:0}.addon input:focus~.addafter{border-color:#75b9f0;box-shadow:0 0 8px rgba(82,168,236,.6)}.addon input:focus~.addafter strong{margin-top:0}.addon .addafter{background:none;color:#a6a6a6;border-width:1px 1px 1px 0;border-radius:2px 2px 0 0;padding:0;border-color:#ada89e}.addon .pager input{border-width:1px}.field .control input[type='text'][disabled],.field .control input[type='text'][disabled]~.addafter,.field .control select[disabled],.field .control select[disabled]~.addafter{background-color:#fff;border-color:#eee;box-shadow:none;color:#999}.field .control input[type='text'][disabled]~.addafter strong,.field .control select[disabled]~.addafter strong{background-color:#fff}.field-price.addon{direction:rtl}.field-price.addon>*{direction:ltr}.field-price.addon .addafter{border-width:1px 0 1px 1px;border-radius:2px 0 0 2px}.field-price.addon input:first-child{border-radius:0 2px 2px 0}.field-price input{border-width:1px 1px 1px 0}.field-price input:focus{box-shadow:0 0 8px rgba(82,168,236,.6)}.field-price input:focus~label.addafter{box-shadow:0 0 8px rgba(82,168,236,.6)}.field-price input~label.addafter strong{margin-left:2px;margin-right:-2px}.field-price.addon>input{width:99px;float:left}.field-price .control{position:relative}.field-price label.mage-error{position:absolute;left:0;top:30px}.version-fieldset .grid-actions{border-bottom:1px solid #f2ebde;margin:0 0 15px;padding:0 0 15px}.navigation>ul,.message-system,.page-header,.page-actions.fixed .page-actions-inner,.page-content,.page-footer{width:auto;min-width:960px;max-width:1300px;margin:0 auto;padding-left:15px;padding-right:15px;box-sizing:border-box;width:100%}.pager label.page,.filters .field-range .group .label,.mass-select-field .label{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visually-hidden.focusable:active,.visually-hidden.focusable:focus,.pager label.page.focusable:active,.pager label.page.focusable:focus,.filters .field-range .group .label.focusable:active,.filters .field-range .group .label.focusable:focus,.mass-select-field .label.focusable:active,.mass-select-field .label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}table th.required:after,.data-table th.required-entry:after,.data-table td.required-entry:after,.grid-actions .filter.required .label span:after,.grid-actions .required:after,.accordion .config .data-table td.required-entry:after{content:'*';color:#e22626;font-weight:400;margin-left:3px}.grid th.required:after,.grid th .required:after{content:'*';color:#f9d4d4;font-weight:400;margin-left:3px}.grid td.col-period,.grid td.col-date,.grid td.col-date_to,.grid td.col-date_from,.grid td.col-ended_at,.grid td.col-created_at,.grid td.col-updated_at,.grid td.col-customer_since,.grid td.col-session_start_time,.grid td.col-last_activity,.grid td.col-email,.grid td.col-name,.grid td.col-sku,.grid td.col-firstname,.grid td.col-lastname,.grid td.col-title,.grid td.col-label,.grid td.col-product,.grid td.col-set_name,.grid td.col-websites,.grid td.col-time,.grid td.col-billing_name,.grid td.col-shipping_name,.grid td.col-phone,.grid td.col-type,.product-options .grouped-items-table .col-name,.product-options .grouped-items-table .col-sku,.sales-order-create-index .data-table .col-product,[class^=' adminhtml-rma-'] .fieldset-wrapper .data-table td,[class^=' adminhtml-rma-'] .grid .col-product_sku,[class^=' adminhtml-rma-'] .grid .col-product_name,.col-grid_segment_name,.adminhtml-catalog-event-index .col-category,[class^=' catalog-search'] .col-search_query,[class^=' catalog-search'] .col-synonym_for,[class^=' catalog-search'] .col-redirect,.adminhtml-urlrewrite-index .col-request_path,.adminhtml-cms-page-index .col-title,.adminhtml-cms-page-index .col-identifier,.adminhtml-cms-hierarchy-index .col-title,.adminhtml-cms-hierarchy-index .col-identifier,.col-banner_name,.adminhtml-widget-instance-index .col-title,.reports-index-search .col-query_text,.adminhtml-rma-item-attribute-index .grid .col-attr-code,.adminhtml-system-store-index .grid td,.catalog-product-attribute-index .col-attr-code,.catalog-product-attribute-index .col-label,.adminhtml-export-index .col-code,.adminhtml-logging-index .grid .col-fullaction,.adminhtml-system-variable-index .grid .col-code,.adminhtml-logging-index .grid .col-info,.dashboard-secondary .dashboard-item tr>td:first-child,.ui-tabs-panel .dashboard-data .col-name,.data-table-td-max .data-table td,[class^=' adminhtml-rma-'] .fieldset-wrapper .accordion .config .data-table td,.data-table-td-max .accordion .config .data-table td,.order-account-information .data-table td,[class^=' adminhtml-rma-'] .rma-request-details .data-table td{overflow:hidden;text-overflow:ellipsis}td.col-period,td.col-date,td.col-date_to,td.col-date_from,td.col-ended_at,td.col-created_at,td.col-updated_at,td.col-customer_since,td.col-session_start_time,td.col-time,td.col-sku,td.col-type,[class^=' adminhtml-rma-'] #rma_items_grid_table .headings th,.adminhtml-process-list .col-action a,.adminhtml-process-list .col-mode{white-space:nowrap}table thead tr th:first-child,table tfoot tr th:first-child,table tfoot tr td:first-child{border-left:0}table thead tr th:last-child,table tfoot tr th:last-child,table tfoot tr td:last-child{border-right:0}.form-inline .grid-actions .label,.form-inline .massaction .label{padding:0;width:auto}.grid .col-action,.grid .col-actions,.grid .col-qty,.grid .col-purchases,.catalog-product-edit .ui-tabs-panel .grid .col-price,.catalog-product-edit .ui-tabs-panel .grid .col-position{width:50px}.grid .col-order-number,.grid .col-real_order_id,.grid .col-invoice-number,.grid .col-increment_id,.grid .col-transaction-id,.grid .col-parent-transaction-id,.grid .col-reference_id,.grid .col-status,.grid .col-price,.grid .col-position,.grid .col-base_grand_total,.grid .col-grand_total,.grid .col-sort_order,.grid .col-carts,.grid .col-priority,.grid .col-severity,.sales-order-create-index .col-in_products,[class^=' reports-'] [class^='col-total'],[class^=' reports-'] [class^='col-average'],[class^=' reports-'] [class^='col-ref-'],[class^=' reports-'] [class^='col-rate'],[class^=' reports-'] [class^='col-tax-amount'],[class^=' adminhtml-customer-'] .col-required,.adminhtml-rma-item-attribute-index .col-required,[class^=' adminhtml-customer-'] .col-system,.adminhtml-rma-item-attribute-index .col-system,[class^=' adminhtml-customer-'] .col-is_visible,.adminhtml-rma-item-attribute-index .col-is_visible,[class^=' adminhtml-customer-'] .col-sort_order,.adminhtml-rma-item-attribute-index .col-sort_order,.catalog-product-attribute-index [class^=' col-is_'],.catalog-product-attribute-index .col-required,.catalog-product-attribute-index .col-system,.adminhtml-test-index .col-is_listed,[class^=' tests-report-test'] [class^='col-inv-']{width:70px}.grid .col-phone,.sales-order-view .grid .col-period,.sales-order-create-index .col-phone,[class^=' adminhtml-rma-'] .grid .col-product_sku,.adminhtml-rma-edit .col-product,.adminhtml-rma-edit .col-sku,.catalog-product-edit .ui-tabs-panel .grid .col-name,.catalog-product-edit .ui-tabs-panel .grid .col-type,.catalog-product-edit .ui-tabs-panel .grid .col-sku,.customer-index-index .grid .col-customer_since,.customer-index-index .grid .col-billing_country_id,[class^=' customer-index-'] .fieldset-wrapper .grid .col-created_at,[class^=' customer-index-'] .accordion .grid .col-created_at{max-width:70px;width:70px}.sales-order-view .grid .col-name,.sales-order-create-index .data-table .col-product,[class^=' adminhtml-rma-'] .grid .col-name,[class^=' adminhtml-rma-'] .grid .col-product,[class^=' catalog-search'] .col-search_query,[class^=' catalog-search'] .col-synonym_for,[class^=' catalog-search'] .col-redirect,.adminhtml-urlrewrite-index .col-request_path,.reports-report-shopcart-abandoned .grid .col-name,.tax-rule-index .grid .col-title,.adminhtml-rma-item-attribute-index .grid .col-attr-code,.dashboard-secondary .dashboard-item tr>td:first-child{max-width:150px;width:150px}[class^=' sales-order-'] .grid .col-name,.catalog-category-edit .grid .col-name,.adminhtml-catalog-event-index .col-category,.adminhtml-banner-edit .grid .col-name,.reports-report-product-lowstock .grid .col-sku,.newsletter-problem-index .grid .col-name,.newsletter-problem-index .grid .col-subject,.newsletter-problem-index .grid .col-product,.adminhtml-rma-item-attribute-index .grid .col-label,.adminhtml-export-index .col-label,.adminhtml-export-index .col-code,.adminhtml-scheduled-operation-index .grid .col-name,.adminhtml-logging-index .grid .col-fullaction,.test-report-customer-wishlist-wishlist .grid .col-name,.test-report-customer-wishlist-wishlist .grid .col-subject,.test-report-customer-wishlist-wishlist .grid .col-product{max-width:220px;width:220px}.grid .col-period,.grid .col-date,.grid .col-date_to,.grid .col-date_from,.grid .col-ended_at,.grid .col-created_at,.grid .col-updated_at,.grid .col-customer_since,.grid .col-session_start_time,.grid .col-last_activity,.grid .col-email,.grid .col-items_total,.grid .col-firstname,.grid .col-lastname,.grid .col-status-default,.grid .col-websites,.grid .col-time,.grid .col-billing_name,.grid .col-shipping_name,.sales-order-index .grid .col-name,.product-options .grouped-items-table .col-name,.product-options .grouped-items-table .col-sku,[class^=' sales-order-view'] .grid .col-customer_name,[class^=' adminhtml-rma-'] .grid .col-product_name,.catalog-product-index .grid .col-name,.catalog-product-review-index .grid .col-name,.catalog-product-review-index .grid .col-title,.customer-index-edit .ui-tabs-panel .grid .col-name,.review-product-index .grid .col-name,.adminhtml-cms-page-index .col-title,.adminhtml-cms-page-index .col-identifier,.catalog-product-attribute-index .col-attr-code,.catalog-product-attribute-index .col-label,.adminhtml-logging-index .grid .col-info{max-width:110px;width:110px}.grid .col-name,.grid .col-product,.col-banner_name,.adminhtml-widget-instance-index .col-title,[class^=' adminhtml-customer-'] .col-label,.adminhtml-rma-item-attribute-index .col-label,.adminhtml-system-variable-index .grid .col-code,.ui-tabs-panel .dashboard-data .col-name,.adminhtml-test-index .col-label{max-width:370px;width:370px}.col-grid_segment_name,.reports-index-search .col-query_text{max-width:570px;width:570px}[class^=' adminhtml-rma-'] .fieldset-wrapper .data-table td,.reports-report-product-lowstock .grid .col-name,.reports-report-shopcart-product .grid .col-name,.reports-report-review-customer .grid .col-name,[class^=' adminhtml-rma-'] .fieldset-wrapper .accordion .config .data-table td{max-width:670px;width:670px}.reports-report-sales-invoiced .grid .col-period,.reports-report-sales-refunde .grid .col-period,[class^=' tests-report-test'] .grid .col-period{width:auto}.grid .col-select,.grid .col-id,.grid .col-number{width:40px}.sales-order-create-index .grid,.sales-order-create-index .grid-actions,.adminhtml-export-index .grid-actions,.adminhtml-export-index .grid{padding-left:0;padding-right:0}[class^=' adminhtml-rma-'] .col-actions a,[class^=' customer-index-'] .col-action a,.adminhtml-notification-index .col-actions a{display:block;margin:0 0 3px;white-space:nowrap}.data-table-td-max .accordion .config .data-table td,.order-account-information .data-table td,[class^=' adminhtml-rma-'] .rma-request-details .data-table td{max-width:250px;width:250px}.catalog-product-edit .ui-tabs-panel .grid .hor-scroll,.catalog-product-index .grid .hor-scroll,.review-product-index .grid .hor-scroll,.adminhtml-rma-edit .hor-scroll{overflow-x:auto}.add-clearer:after,.massaction:after,.navigation>ul:after{content:"";display:table;clear:both}.test-content{width:calc(20px + 100*0.2)}.test-content:before{content:'.test {\A ' attr(data-attribute) ': 0.2em;' '\A content:\'';white-space:pre}.test-content:after{content:' Test\';\A}' "\A" '\A.test + .test._other ~ ul > li' " {\A height: @var;\A content: ' + ';\A}";white-space:pre}.test-content-calc{width:calc((100%/12*2) - 10px)}.test-svg-xml-image{background:url('data:image/svg+xml;utf8,') no-repeat left center} \ No newline at end of file +table>caption{margin-bottom:5px}table thead{background:#676056;color:#f7f3eb}table thead .headings{background:#807a6e}table thead a{color:#f7f3eb;display:block}table thead a label{color:#f7f3eb;cursor:pointer;display:block}table thead a:hover,table thead a:focus{color:#dac7a2;text-decoration:none}table tfoot{background:#f2ebde;color:#676056}table tfoot tr th,table tfoot tr td{text-align:left}table th{background:0 0;border:solid #cac3b4;border-width:0 1px;font-size:14px;padding:6px 10px;text-align:center}table td{border:solid #cac3b4;border-width:0 1px;padding:6px 10px 7px;vertical-align:top}table tbody tr td{background:#fff;color:#676056;padding-top:12px}table tbody tr td:first-child{border-left:0}table tbody tr td:first-child input[type=checkbox]{margin:0}table tbody tr td:last-child{border-right:0}table tbody tr:last-child th,table tbody tr:last-child td{border-bottom-width:1px}table tbody tr:nth-child(odd) td,table tbody tr:nth-child(odd) th{background-color:#f7f3eb}table tbody.even tr td{background:#fff}table tbody.odd tr td{background:#f7f3eb}table .dropdown-menu li{padding:7px 15px;line-height:14px;cursor:pointer}table .col-draggable .draggable-handle{float:left;position:relative;top:0}.not-sort{padding-right:10px}.sort-arrow-asc,.sort-arrow-desc{padding-right:10px;position:relative}.sort-arrow-asc:after,.sort-arrow-desc:after{right:-11px;top:-1px;position:absolute;width:23px}.sort-arrow-asc:hover:after,.sort-arrow-desc:hover:after{color:#dac7a2}.sort-arrow-asc{display:inline-block;text-decoration:none}.sort-arrow-asc:after{font-family:'icons-blank-theme';content:'\e626';font-size:13px;line-height:inherit;color:#f7f3eb;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.sort-arrow-asc:hover:after{color:#dac7a2}.sort-arrow-desc{display:inline-block;text-decoration:none}.sort-arrow-desc:after{font-family:'icons-blank-theme';content:'\e623';font-size:13px;line-height:inherit;color:#f7f3eb;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.sort-arrow-desc:hover:after{color:#dac7a2}.grid-actions .input-text,.pager .input-text,.massaction .input-text,.filter .input-text,.grid-actions select,.pager select,.massaction select,.filter select,.grid-actions .select,.pager .select,.massaction .select,.filter .select{border-color:#989287;box-shadow:none;border-radius:1px;height:28px;margin:0 10px 0 0}.filter th{border:0 solid #676056;padding:6px 3px;vertical-align:top}.filter .ui-datepicker-trigger{cursor:pointer;margin-top:2px}.filter .input-text{padding:0 5px}.filter .range-line:not(:last-child){margin-bottom:5px}.filter .date{padding-right:28px;position:relative;display:inline-block;text-decoration:none}.filter .date .hasDatepicker{vertical-align:top;width:99%}.filter .date img{cursor:pointer;height:25px;width:25px;right:0;position:absolute;vertical-align:middle;z-index:2;opacity:0}.filter .date:before{font-family:'icons-blank-theme';content:'\e612';font-size:42px;line-height:30px;color:#f7f3eb;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.filter .date:hover:before{color:#dac7a2}.filter .date:before{height:29px;margin-left:5px;position:absolute;right:-3px;top:-3px;width:35px}.filter select{border-color:#cac3b4;margin:0;padding:0;width:99%}.filter input.input-text{border-color:#cac3b4;margin:0;width:99%}.filter input.input-text::-webkit-input-placeholder{color:#989287 !important;text-transform:lowercase}.filter input.input-text::-moz-placeholder{color:#989287 !important;text-transform:lowercase}.filter input.input-text:-moz-placeholder{color:#989287 !important;text-transform:lowercase}.filter input.input-text:-ms-input-placeholder{color:#989287 !important;text-transform:lowercase}.grid{background:#fff;color:#676056;font-size:13px;font-weight:400;padding:15px}.grid table{width:100%}.grid tbody tr.selected th,.grid tbody tr.selected td,.grid tbody tr:hover th,.grid tbody tr:hover td,.grid tbody tr:nth-child(odd):hover th,.grid tbody tr:nth-child(odd):hover td{background-color:#f2ebde;cursor:pointer}.grid tbody tr.selected th.empty-text,.grid tbody tr.selected td.empty-text,.grid tbody tr:hover th.empty-text,.grid tbody tr:hover td.empty-text,.grid tbody tr:nth-child(odd):hover th.empty-text,.grid tbody tr:nth-child(odd):hover td.empty-text{background-color:#f7f3eb;cursor:default}.grid .empty-text{font:400 20px/1.2 'Open Sans',sans-serif;text-align:center;white-space:nowrap}.grid .col-sku{max-width:100px;width:100px}.grid .col-select,.grid .col-massaction{text-align:center}.grid .editable .input-text{width:65px}.grid .col-actions .action-select{background:#fff;border-color:#989287;height:28px;margin:0;padding:4px 4px 5px;width:80px}.grid .col-position.editable{white-space:nowrap}.grid .col-position.editable .input-text{margin:-7px 5px 0;width:70%}.eq-ie9 .hor-scroll{display:inline-block;min-height:0;overflow-y:hidden;overflow-x:auto;width:100%}.data-table{border-collapse:separate;width:100%}.data-table thead,.data-table tfoot,.data-table th,.accordion .config .data-table thead th,.accordion .config .data-table tfoot td,.accordion .config .accordion .config .data-table tfoot td th{background:#fff;color:#676056;font-size:13px;font-weight:600}.data-table th{text-align:left}.data-table thead th,.accordion .config .data-table thead th th,.accordion .config .data-table tfoot td th,.accordion .config .accordion .config .data-table tfoot td th th{border:solid #c9c2b8;border-width:0 0 1px;padding:7px}.data-table td,.data-table tbody tr th,.data-table tbody tr td,.accordion .config .data-table td{background:#fff;border-width:0;padding:5px 7px;vertical-align:middle}.data-table tbody tr:nth-child(odd) th,.data-table tbody tr:nth-child(odd) td,.accordion .config .data-table tbody tr:nth-child(odd) td{background:#fbfaf6}.data-table tbody.odd tr th,.data-table tbody.odd tr td{background:#fbfaf6}.data-table tbody.even tr th,.data-table tbody.even tr td{background:#fff}.data-table tfoot tr:last-child th,.data-table tfoot tr:last-child td,.data-table .accordion .config .data-table tfoot tr:last-child td{border:0}.data-table.order-tables tbody td{vertical-align:top}.data-table.order-tables tbody:hover tr th,.data-table.order-tables tbody:hover tr td{background:#f7f3eb}.data-table.order-tables tfoot td{background:#f2ebde;color:#676056;font-size:13px;font-weight:600}.data-table input[type=text]{width:98%;padding-left:1%;padding-right:1%}.data-table select{margin:0;box-sizing:border-box}.data-table .col-actions .actions-split{margin-top:4px}.data-table .col-actions .actions-split [class^=action-]{background:0 0;border:1px solid #c8c3b5;padding:3px 5px;color:#bbb3a6;font-size:12px}.data-table .col-actions .actions-split [class^=action-]:first-child{border-right:0}.data-table .col-actions .actions-split .dropdown-menu{margin-top:-1px}.data-table .col-actions .actions-split .dropdown-menu a{display:block;color:#333;text-decoration:none}.data-table .col-actions .actions-split.active .action-toggle{position:relative;border-bottom-right-radius:0;box-shadow:none;background:#fff}.data-table .col-actions .actions-split.active .action-toggle:after{position:absolute;top:100%;left:0;right:0;height:2px;margin-top:-1px;background:#fff;content:'';z-index:2}.data-table .col-actions .actions-split.active .action-toggle .dropdown-menu{border-top-right-radius:0}.data-table .col-default{white-space:nowrap;text-align:center;vertical-align:middle}.data-table .col-delete{text-align:center;width:32px}.data-table .col-file{white-space:nowrap}.data-table .col-file input,.data-table .col-file .input-text{margin:0 5px;width:40%}.data-table .col-file input:first-child,.data-table .col-file .input-text:first-child{margin-left:0}.data-table .col-actions-add{padding:10px 0}.grid-actions{background:#fff;font-size:13px;line-height:28px;padding:10px 15px;position:relative}.grid-actions+.grid{padding-top:5px}.grid-actions .export,.grid-actions .filter-actions{float:right;margin-left:10px;vertical-align:top}.grid-actions .import{display:block;vertical-align:top}.grid-actions .action-reset{background:0 0;border:0;display:inline;line-height:1.42857143;margin:0;padding:0;color:#1979c3;text-decoration:none;margin:6px 10px 0 0;vertical-align:top}.grid-actions .action-reset:visited{color:purple;text-decoration:none}.grid-actions .action-reset:hover{color:#006bb4;text-decoration:underline}.grid-actions .action-reset:active{color:#ff5501;text-decoration:underline}.grid-actions .action-reset:hover{color:#006bb4}.grid-actions .action-reset:hover,.grid-actions .action-reset:active,.grid-actions .action-reset:focus{background:0 0;border:0}.grid-actions .action-reset.disabled,.grid-actions .action-reset[disabled],fieldset[disabled] .grid-actions .action-reset{color:#1979c3;text-decoration:underline;cursor:default;pointer-events:none;opacity:.5}.grid-actions .import .label,.grid-actions .export .label,.massaction>.entry-edit .label{margin:0 14px 0 0;vertical-align:inherit}.grid-actions .import .action-,.grid-actions .export .action-,.grid-actions .filter-actions .action-,.massaction>.entry-edit .action-{vertical-align:inherit}.grid-actions .filter .date{float:left;margin:0 15px 0 0;position:relative}.grid-actions .filter .date:before{color:#676056;top:1px}.grid-actions .filter .date:hover:before{color:#31302b}.grid-actions .filter .label{margin:0}.grid-actions .filter .hasDatepicker{margin:0 5px;width:80px}.grid-actions .filter .show-by .select{margin-left:5px;padding:4px 4px 5px;vertical-align:top;width:auto}.grid-actions .filter.required:after{content:''}.grid-actions img{vertical-align:middle;height:22px;width:22px}.grid-actions .validation-advice{background:#f9d4d4;border:1px solid #e22626;border-radius:3px;color:#e22626;margin:5px 0 0;padding:3px 7px;position:absolute;white-space:nowrap;z-index:5}.grid-actions .validation-advice:before{width:0;height:0;border:5px solid transparent;border-bottom-color:#e22626;content:'';left:50%;margin-left:-5px;position:absolute;top:-11px}.grid-actions input[type=text].validation-failed{border-color:#e22626;box-shadow:0 0 8px rgba(226,38,38,.6)}.grid-actions .link-feed{white-space:nowrap}.pager{font-size:13px}.grid .pager{margin:15px 0 0;position:relative;text-align:center}.pager .pages-total-found{margin-right:25px}.pager .view-pages .select{margin:0 5px}.pager .link-feed{font-size:12px;margin:7px 15px 0 0;position:absolute;right:0;top:0}.pager .action-previous,.pager .action-next{background:0 0;border:0;display:inline;line-height:1.42857143;margin:0;padding:0;color:#1979c3;text-decoration:none;line-height:.6;overflow:hidden;width:20px}.pager .action-previous:visited,.pager .action-next:visited{color:purple;text-decoration:none}.pager .action-previous:hover,.pager .action-next:hover{color:#006bb4;text-decoration:underline}.pager .action-previous:active,.pager .action-next:active{color:#ff5501;text-decoration:underline}.pager .action-previous:hover,.pager .action-next:hover{color:#006bb4}.pager .action-previous:hover,.pager .action-next:hover,.pager .action-previous:active,.pager .action-next:active,.pager .action-previous:focus,.pager .action-next:focus{background:0 0;border:0}.pager .action-previous.disabled,.pager .action-next.disabled,.pager .action-previous[disabled],.pager .action-next[disabled],fieldset[disabled] .pager .action-previous,fieldset[disabled] .pager .action-next{color:#1979c3;text-decoration:underline;cursor:default;pointer-events:none;opacity:.5}.pager .action-previous:before,.pager .action-next:before{margin-left:-10px}.pager .action-previous.disabled,.pager .action-next.disabled{opacity:.3}.pager .action-previous{display:inline-block;text-decoration:none}.pager .action-previous>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.pager .action-previous>span.focusable:active,.pager .action-previous>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-previous>span.focusable:active,.pager .action-previous>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-previous:before{font-family:'icons-blank-theme';content:'\e617';font-size:40px;line-height:inherit;color:#026294;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.pager .action-previous:hover:before{color:#007dbd}.pager .action-next{display:inline-block;text-decoration:none}.pager .action-next>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.pager .action-next>span.focusable:active,.pager .action-next>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-next>span.focusable:active,.pager .action-next>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.pager .action-next:before{font-family:'icons-blank-theme';content:'\e608';font-size:40px;line-height:inherit;color:#026294;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.pager .action-next:hover:before{color:#007dbd}.pager .input-text{height:25px;line-height:16px;margin-right:5px;text-align:center;width:25px;vertical-align:top}.pager .pages-total{line-height:25px;vertical-align:top}.massaction{background:#fff;border-top:1px solid #f2ebde;font-size:13px;line-height:28px;padding:15px 15px 0}.massaction>.entry-edit{float:right}.massaction>.entry-edit .field-row{display:inline-block;vertical-align:top}.massaction>.entry-edit .validation-advice{display:none !important}.massaction>.entry-edit .form-inline{display:inline-block}.massaction>.entry-edit .label{padding:0;width:auto}.massaction>.entry-edit .action-{vertical-align:top}.massaction .select.validation-failed{border:1px dashed #e22626;background:#f9d4d4}.grid-severity-critical,.grid-severity-major,.grid-severity-notice,.grid-severity-minor{background:#feeee1;border:1px solid #ed4f2e;color:#ed4f2e;display:block;padding:0 3px;font-weight:700;line-height:17px;text-transform:uppercase;text-align:center}.grid-severity-critical,.grid-severity-major{border-color:#e22626;background:#f9d4d4;color:#e22626}.grid-severity-notice{border-color:#5b8116;background:#d0e5a9;color:#185b00}.grid tbody td input[type=text],.data-table tbody td input[type=text],.grid tbody th input[type=text],.data-table tbody th input[type=text],.grid tbody td .input-text,.data-table tbody td .input-text,.grid tbody th .input-text,.data-table tbody th .input-text,.grid tbody td select,.data-table tbody td select,.grid tbody th select,.data-table tbody th select,.grid tbody td .select,.data-table tbody td .select,.grid tbody th .select,.data-table tbody th .select{width:99%}.ui-tabs-panel .grid .col-sku{max-width:150px;width:150px}.col-indexer_status,.col-indexer_mode{width:160px}.fieldset-wrapper .grid-actions+.grid{padding-top:15px}.fieldset-wrapper .grid-actions{padding:10px 0 0}.fieldset-wrapper .grid{padding:0}.fieldset-wrapper .massaction{padding:0;border-top:none;margin-bottom:15px}.accordion .grid{padding:0}.ui-dialog-content .grid-actions,.ui-dialog-content .grid{padding-left:0;padding-right:0}.qty-table td{border:0;padding:0 5px 3px}.sales-order-create-index .sales-order-create-index .grid table .action-configure{float:right}.sales-order-create-index .data-table .border td{padding-bottom:15px}.sales-order-create-index .actions.update{margin:10px 0}.adminhtml-order-shipment-new .grid .col-product{max-width:770px;width:770px}.customer-index-index .grid .col-name{max-width:90px;width:90px}.customer-index-index .grid .col-billing_region{width:70px}.adminhtml-cms-hierarchy-index .col-title,.adminhtml-cms-hierarchy-index .col-identifier{max-width:410px;width:410px}.adminhtml-widget-instance-edit .grid-chooser .control{margin-top:-19px;width:80%}.eq-ie9 .adminhtml-widget-instance-edit .grid-chooser .control{margin-top:-18px}.adminhtml-widget-instance-edit .grid-chooser .control .grid-actions{padding:0 0 15px}.adminhtml-widget-instance-edit .grid-chooser .control .grid{padding:0}.adminhtml-widget-instance-edit .grid-chooser .control .addon input:last-child,.adminhtml-widget-instance-edit .grid-chooser .control .addon select:last-child{border-radius:0}.reports-report-product-sold .grid .col-name{max-width:720px;width:720px}.adminhtml-system-store-index .grid td{max-width:310px}.adminhtml-system-currency-index .grid{padding-top:0}.adminhtml-system-currency-index .col-currency-edit-rate{min-width:40px}.adminhtml-system-currency-index .col-base-currency{font-weight:700}.adminhtml-system-currency-index .old-rate{display:block;margin-top:3px;text-align:center}.adminhtml-system-currency-index .hor-scroll{overflow-x:auto;min-width:970px}.adminhtml-system-currencysymbol-index .col-currency{width:35%}.adminhtml-system-currencysymbol-index .grid .input-text{margin:0 10px 0 0;width:50%}.catalog-product-set-index .col-set_name{max-width:930px;width:930px}.adminhtml-export-index .grid td{vertical-align:middle}.adminhtml-export-index .grid .input-text-range{margin:0 10px 0 5px;width:37%}.adminhtml-export-index .grid .input-text-range-date{margin:0 5px;width:32%}.adminhtml-export-index .ui-datepicker-trigger{display:inline-block;margin:-3px 10px 0 0;vertical-align:middle}.adminhtml-notification-index .grid .col-select,.adminhtml-cache-index .grid .col-select,.adminhtml-process-list .grid .col-select,.indexer-indexer-list .grid .col-select{width:10px}@font-face{font-family:'icons-blank-theme';src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot');src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot?#iefix') format('embedded-opentype'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff2') format('woff2'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff') format('woff'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.ttf') format('truetype'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.svg#icons-blank-theme') format('svg');font-weight:400;font-style:normal}@font-face{font-family:'icons-blank-theme';src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot');src:url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.eot?#iefix') format('embedded-opentype'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff2') format('woff2'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.woff') format('woff'),url('../fonts/Blank-Theme-Icons/Blank-Theme-Icons.ttf') format('truetype');font-weight:400;font-style:normal}.navigation{background-color:#676056;position:relative;z-index:5}.navigation .level-0.reverse>.submenu{right:1px}.navigation>ul{position:relative;text-align:right}.navigation .level-0>.submenu{display:none;position:absolute;top:100%;padding:19px 13px}.navigation .level-0>.submenu a{display:block;color:#676056;font-size:13px;font-weight:400;line-height:1.385;padding:3px 12px 3px;text-decoration:none}.navigation .level-0>.submenu a:focus,.navigation .level-0>.submenu a:hover{text-decoration:underline}.navigation .level-0>.submenu a:hover{color:#fff;background:#989287;text-decoration:none}.navigation .level-0>.submenu li{margin-bottom:1px}.navigation .level-0>.submenu a[href="#"]{cursor:default;display:block;color:#676056;font-size:14px;font-weight:700;line-height:1;margin:7px 0 6px;padding:0 12px}.navigation .level-0>.submenu a[href="#"]:focus,.navigation .level-0>.submenu a[href="#"]:hover{color:#676056;font-size:14px;font-weight:700;background:0 0;text-decoration:none}.navigation .level-0{display:inline-block;float:left;text-align:left;transition:display .15s ease-out}.navigation .level-0>a{background:0 0;display:block;padding:12px 13px 0;color:#f2ebde;font-size:13px;font-weight:600;text-transform:uppercase;text-decoration:none;transition:background .15s ease-out}.navigation .level-0>a:after{content:"";display:block;margin-top:10px;height:3px;font-size:0}.navigation .level-0.active>a{font-weight:700}.navigation .level-0.active>a:after{background:#ef672f}.navigation .level-0.hover.recent>a{background:#fff;color:#676056;font-size:13px;font-weight:600}.navigation .level-0.hover.recent>a:after{background:0 0}.navigation .level-0.hover.recent.active>a{font-weight:700}.navigation .level-0>.submenu{opacity:0;visibility:hidden}.navigation .level-0.recent.hover>.submenu{opacity:1;visibility:visible}.no-js .navigation .level-0:hover>.submenu,.no-js .navigation .level-0.hover>.submenu,.no-js .navigation .level-0>a:focus+.submenu{display:block}.navigation .level-0>.submenu{background:#fff;box-shadow:0 3px 3px rgba(50,50,50,.15)}.navigation .level-0>.submenu li{max-width:200px}.navigation .level-0>.submenu>ul{white-space:nowrap}.navigation .level-0>.submenu .column{display:inline-block;margin-left:40px;vertical-align:top}.navigation .level-0>.submenu .column:first-child{margin-left:0}.navigation .level-0 .submenu .level-1{white-space:normal}.navigation .level-0.parent .submenu .level-1.parent{margin:17px 0 25px}.navigation .level-0.parent .level-1.parent:first-child{margin-top:0}.navigation .level-2 .submenu{margin-left:7px}.navigation .level-0>.submenu .level-2>a[href="#"]{font-size:13px;margin-top:10px;margin-left:7px}.navigation .level-2>.submenu a{font-size:12px;line-height:1.231}.navigation .level-0>.submenu .level-3>a[href="#"],.navigation .level-3 .submenu{margin-left:15px}.navigation .level-0.item-system,.navigation .level-0.item-stores{float:none}.navigation .level-0.item-system>.submenu,.navigation .level-0.item-stores>.submenu{left:auto;right:1px}.adminhtml-dashboard-index .col-1-layout{max-width:1300px;border:none;border-radius:0;padding:0;background:#f7f3eb}.dashboard-inner{padding-top:35px}.dashboard-inner:before,.dashboard-inner:after{content:"";display:table}.dashboard-inner:after{clear:both}.dashboard-inner:before,.dashboard-inner:after{content:"";display:table}.dashboard-inner:after{clear:both}.dashboard-secondary{float:left;width:32%;margin:0 1.5%}.dashboard-main{float:right;width:65%}.dashboard-diagram-chart{max-width:100%;height:auto}.dashboard-diagram-nodata,.dashboard-diagram-switcher{padding:20px 0}.dashboard-diagram-image{background:#fff url(../mui/images/ajax-loader-small.gif) no-repeat 50% 50%}.dashboard-container .ui-tabs-panel{background-color:#fff;min-height:40px;padding:15px}.dashboard-store-stats{margin-top:35px}.dashboard-store-stats .ui-tabs-panel{background:#fff url(../mui/images/ajax-loader-small.gif) no-repeat 50% 50%}.dashboard-item{margin-bottom:30px}.dashboard-item-header{margin-left:5px}.dashboard-item.dashboard-item-primary{margin-bottom:35px}.dashboard-item.dashboard-item-primary .title{font-size:22px;margin-bottom:5px}.dashboard-item.dashboard-item-primary .dashboard-sales-value{display:block;text-align:right;font-weight:600;font-size:30px;margin-right:12px;padding-bottom:5px}.dashboard-item.dashboard-item-primary:first-child{color:#ef672f}.dashboard-item.dashboard-item-primary:first-child .title{color:#ef672f}.dashboard-totals{background:#fff;padding:50px 15px 25px}.dashboard-totals-list{margin:0;padding:0;list-style:none none}.dashboard-totals-list:before,.dashboard-totals-list:after{content:"";display:table}.dashboard-totals-list:after{clear:both}.dashboard-totals-list:before,.dashboard-totals-list:after{content:"";display:table}.dashboard-totals-list:after{clear:both}.dashboard-totals-item{float:left;width:18%;margin-left:7%;padding-top:15px;border-top:2px solid #cac3b4}.dashboard-totals-item:first-child{margin-left:0}.dashboard-totals-label{display:block;font-size:16px;font-weight:600;padding-bottom:2px}.dashboard-totals-value{color:#ef672f;font-size:20px}.dashboard-data{width:100%}.dashboard-data thead{background:0 0}.dashboard-data thead tr{background:0 0}.dashboard-data th,.dashboard-data td{border:none;padding:10px 12px;text-align:right}.dashboard-data th:first-child,.dashboard-data td:first-child{text-align:left}.dashboard-data th{color:#676056;font-weight:600}.dashboard-data td{background-color:transparent}.dashboard-data tbody tr:hover td{background-color:transparent}.dashboard-data tbody tr:nth-child(odd) td,.dashboard-data tbody tr:nth-child(odd):hover td,.dashboard-data tbody tr:nth-child(odd) th,.dashboard-data tbody tr:nth-child(odd):hover th{background-color:#e1dbcf}.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd) td,.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd):hover td,.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd) th,.ui-tabs-panel .dashboard-data tbody tr:nth-child(odd):hover th{background-color:#f7f3eb}.dashboard-data td.empty-text{text-align:center}.ui-tabs-panel .dashboard-data{background-color:#fff}.mage-dropdown-dialog.ui-dialog .ui-dialog-content{overflow:visible}.mage-dropdown-dialog.ui-dialog .ui-dialog-buttonpane{padding:0}.message-system-inner{background:#f7f3eb;border:1px solid #c0bbaf;border-top:0;border-radius:0 0 5px 5px;float:right;overflow:hidden}.message-system-unread .message-system-inner{float:none}.message-system-list{margin:0;padding:0;list-style:none;float:left}.message-system .message-system-list{width:75%}.message-system-list li{padding:5px 13px 7px 36px;position:relative}.message-system-short{padding:5px 13px 7px;float:right}.message-system-short span{display:inline-block;margin-left:7px;border-left:1px #d1ccc3 solid}.message-system-short span:first-child{border:0;margin-left:0}.message-system-short a{padding-left:27px;position:relative;height:16px}.message-system .message-system-short a:before,.message-system-list li:before{font-family:'MUI-Icons';font-style:normal;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;height:16px;width:16px;font-size:16px;line-height:16px;text-align:center;position:absolute;left:7px;top:2px}.message-system-list li:before{top:5px;left:13px}.message-system .message-system-short .warning a:before,.message-system-list li.warning:before{content:"\e006";color:#f2a825}.message-system .message-system-short .error a:before,.message-system-list li.error:before{content:"\e086";font-family:'MUI-Icons';color:#c00815}.ui-dialog .message-system-list{margin-bottom:25px}.sales-order-create-index .order-errors .notice{color:#ed4f2e;font-size:11px;margin:5px 0 0}.order-errors .fieldset-wrapper-title .title{box-sizing:border-box;background:#fffbf0;border:1px solid #d87e34;border-radius:5px;color:#676056;font-size:14px;margin:20px 0;padding:10px 26px 10px 35px;position:relative}.order-errors .fieldset-wrapper-title .title:before{position:absolute;left:11px;top:50%;margin-top:-11px;width:auto;height:auto;font-family:'MUI-Icons';font-style:normal;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;font-size:16px;line-height:inherit;content:'\e046';color:#d87e34}.search-global.miniform{position:relative;z-index:1000;display:inline-block;vertical-align:top;margin:6px 10px 0}.search-global.miniform .mage-suggest{border:0;border-radius:0}.search-global-actions{display:none}.search-global-field{margin:0}.search-global-field .label{position:absolute;right:4px;z-index:2;cursor:pointer;display:inline-block;text-decoration:none}.search-global-field .label>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.search-global-field .label>span.focusable:active,.search-global-field .label>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.search-global-field .label>span.focusable:active,.search-global-field .label>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.search-global-field .label:before{font-family:'MUI-Icons';content:"\e01f";font-size:18px;line-height:29px;color:#cac3b4;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.search-global-field .control{width:48px;overflow:hidden;opacity:0;transition:all .3s ease}.search-global-field .control input[type=text]{background:0 0;border:none;width:100%}.search-global-field.active{z-index:2}.search-global-field.active .label:before{display:none}.search-global-field.active .control{overflow:visible;opacity:1;transition:all .3s ease;width:300px}.search-global-menu{box-sizing:border-box;display:block;width:100%}.notifications-summary{display:inline-block;text-align:left;position:relative;z-index:1}.notifications-summary.active{z-index:999}.notifications-action{color:#f2ebde;padding:12px 22px 11px;text-transform:capitalize;display:inline-block;text-decoration:none}.notifications-action:before{font-family:"MUI-Icons";content:"\e06e";font-size:18px;line-height:18px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.notifications-action:visited,.notifications-action:focus,.notifications-action:active,.notifications-action:hover{color:#f2ebde;text-decoration:none}.notifications-action.active{background-color:#fff;color:#676056}.notifications-action .text{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.notifications-action .text.focusable:active,.notifications-action .text.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-action .text.focusable:active,.notifications-action .text.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-action .qty.counter{display:inline-block;background:#ed4f2e;color:#f2ebde;font-size:12px;line-height:12px;font-weight:700;padding:1px 3px;position:absolute;top:6px;left:50%;border-radius:4px}.notifications-list{width:300px;padding:0;margin:0}.notifications-list .last{padding:10px;text-align:center;font-size:12px}.notifications-summary .notifications-entry{padding:15px;color:#676056;font-size:11px;font-weight:400}.notifications-entry{position:relative;z-index:1}.notifications-entry:hover .action{display:block}.notifications-entry-title{padding-right:15px;color:#ed4f2e;font-size:12px;font-weight:600;display:block;margin-bottom:10px}.notifications-entry-description{line-height:1.3;display:block;max-height:3.9em;overflow:hidden;margin-bottom:10px;text-overflow:ellipsis}.notifications-close.action{position:absolute;z-index:1;top:12px;right:12px;display:inline-block;background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400;display:none}.notifications-close.action>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.notifications-close.action>span.focusable:active,.notifications-close.action>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-close.action>span.focusable:active,.notifications-close.action>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-close.action:before{font-family:'MUI-Icons';content:"\e07f";font-size:16px;line-height:inherit;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.notifications-close.action:focus,.notifications-close.action:active{background:0 0;border:none}.notifications-close.action:hover{background:0 0;border:none}.notifications-close.action.disabled,.notifications-close.action[disabled],fieldset[disabled] .notifications-close.action{cursor:not-allowed;pointer-events:none;opacity:.5}.notifications-dialog-content{display:none}.notifications-critical .notifications-entry-title{padding-left:25px;display:inline-block;text-decoration:none}.notifications-critical .notifications-entry-title:before{font-family:'MUI-Icons';content:"\e086";font-size:18px;line-height:18px;color:#c00815;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.notifications-critical .notifications-entry-title:before{position:absolute;margin-left:-25px}.notifications-dialog-content .notifications-entry-time{color:#8c867e;font-size:13px;font-family:Helvetica,Arial,sans-serif;position:absolute;right:17px;bottom:27px;text-align:right}.notifications-url{display:inline-block;text-decoration:none}.notifications-url>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.notifications-url>span.focusable:active,.notifications-url>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-url>span.focusable:active,.notifications-url>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.notifications-url:after{font-family:'MUI-Icons';content:"\e084";font-size:16px;line-height:inherit;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:-2px 0 0 10px}.notifications-dialog-content .notifications-entry-title{font-size:15px}.locale-switcher-field{white-space:nowrap;float:left}.locale-switcher-field .control,.locale-switcher-field .label{vertical-align:middle;margin:0 10px 0 0;display:inline-block}.locale-switcher-select{-webkit-appearance:none;-moz-appearance:none;-ms-appearance:none;appearance:none;border:1px solid #ada89e;max-width:200px;height:31px;background:url("../images/select-bg.svg") no-repeat 100% 50%;background-size:30px 60px;padding-right:29px;text-indent:.01px;text-overflow:''}.locale-switcher-select::-ms-expand{display:none}.lt-ie10 .locale-switcher-select{background-image:none;padding-right:4px}@-moz-document url-prefix(){.locale-switcher-select{background-image:none}}@-moz-document url-prefix(){.locale-switcher-select{background-image:none}}.mage-suggest{text-align:left;box-sizing:border-box;position:relative;display:inline-block;vertical-align:top;width:100%;background-color:#fff;border:1px solid #ada89e;border-radius:2px}.mage-suggest:after{position:absolute;top:3px;right:3px;bottom:0;width:22px;text-align:center;font-family:'MUI-Icons';font-style:normal;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;content:'\e01f';font-size:18px;color:#b2b2b2}.mage-suggest input[type=search],.mage-suggest input.search{width:100%;border:none;background:0 0;padding-right:30px}.mage-suggest.category-select input[type=search],.mage-suggest.category-select input.search{height:26px}.mage-suggest-dropdown{position:absolute;left:0;right:0;top:100%;margin:1px -1px 0;border:1px solid #cac2b5;background:#fff;box-shadow:0 2px 4px rgba(0,0,0,.2);z-index:990}.mage-suggest-dropdown ul{margin:0;padding:0;list-style:none}.mage-suggest-dropdown li{border-bottom:1px solid #e5e5e5;padding:0}.mage-suggest-dropdown li a{display:block}.mage-suggest-dropdown li a.ui-state-focus{background:#f5f5f5}.mage-suggest-dropdown li a,.mage-suggest-dropdown .jstree li a:hover,.mage-suggest-dropdown .jstree .jstree-hovered,.mage-suggest-dropdown .jstree .jstree-clicked{padding:6px 12px 5px;text-decoration:none;color:#333}.mage-suggest-dropdown .jstree li a:hover,.mage-suggest-dropdown .jstree .jstree-hovered,.mage-suggest-dropdown .jstree .jstree-clicked{border:none}.mage-suggest-dropdown .jstree li{border-bottom:0}.mage-suggest-dropdown .jstree li a{display:inline-block}.mage-suggest-dropdown .jstree .mage-suggest-selected>a{color:#000;background:#f1ffeb}.field-category_ids .mage-suggest-dropdown,.field-new_category_parent .mage-suggest-dropdown{max-height:200px;overflow:auto}.mage-suggest-dropdown .jstree .mage-suggest-selected>a:hover,.mage-suggest-dropdown .jstree .mage-suggest-selected>.jstree-hovered,.mage-suggest-dropdown .jstree .mage-suggest-selected>.jstree-clicked,.mage-suggest-dropdown .jstree .mage-suggest-selected.mage-suggest-not-active>.jstree-hovered,.mage-suggest-dropdown .jstree .mage-suggest-selected.mage-suggest-not-active>.jstree-clicked{background:#e5ffd9}.mage-suggest-dropdown .jstree .mage-suggest-not-active>a{color:#d4d4d4}.mage-suggest-dropdown .jstree .mage-suggest-not-active>a:hover,.mage-suggest-dropdown .jstree .mage-suggest-not-active>.jstree-hovered,.mage-suggest-dropdown .jstree .mage-suggest-not-active>.jstree-clicked{background:#f5f5f5}.mage-suggest-dropdown .category-path{font-size:11px;margin-left:10px;color:#9ba8b5}.suggest-expandable .action-dropdown .action-toggle{display:inline-block;max-width:500px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:0 0;border:none;box-shadow:none;color:#676056;font-size:12px;padding:5px 4px;filter:none}.suggest-expandable .action-dropdown .action-toggle span{display:inline}.suggest-expandable .action-dropdown .action-toggle:before{display:inline-block;float:right;margin-left:4px;font-size:13px;color:#b2b0ad}.suggest-expandable .action-dropdown .action-toggle:hover:before{color:#7e7e7e}.suggest-expandable .dropdown-menu{margin:1px 0 0;left:0;right:auto;width:245px;z-index:4}.suggest-expandable .mage-suggest{border:none;border-radius:3px 3px 0 0}.suggest-expandable .mage-suggest:after{top:10px;right:8px}.suggest-expandable .mage-suggest-inner .title{margin:0;padding:0 10px 4px;text-transform:uppercase;color:#a6a098;font-size:12px;border-bottom:1px solid #e5e5e5}.suggest-expandable .mage-suggest-inner>input[type=search],.suggest-expandable .mage-suggest-inner>input.search{position:relative;margin:6px 5px 5px;padding-right:20px;border:1px solid #ada89e;width:236px;z-index:1}.suggest-expandable .mage-suggest-inner>input.ui-autocomplete-loading,.suggest-expandable .mage-suggest-inner>input.mage-suggest-state-loading{background:#fff url("../mui/images/ajax-loader-small.gif") no-repeat 190px 50%}.suggest-expandable .mage-suggest-dropdown{margin-top:0;border-top:0;border-radius:0 0 3px 3px;max-height:300px;overflow:auto;width:100%;float:left}.suggest-expandable .mage-suggest-dropdown ul{margin:0;padding:0;list-style:none}.suggest-expandable .action-show-all:hover,.suggest-expandable .action-show-all:active,.suggest-expandable .action-show-all:focus,.suggest-expandable .action-show-all[disabled]{border-top:1px solid #e5e5e5;display:block;width:100%;padding:8px 10px 10px;text-align:left;font:12px/1.333 Arial,Verdana,sans-serif;color:#676056}.product-actions .suggest-expandable{max-width:500px;float:left;margin-top:1px}.page-actions.fixed #product-template-suggest-container{display:none}.catalog-category-edit .col-2-left-layout:before{display:none}.category-content .ui-tabs-panel .fieldset{padding-top:40px}.category-content .ui-tabs-panel .fieldset .legend{display:none}.attributes-edit-form .field:not(.field-weight) .addon{display:block;position:relative}.attributes-edit-form .field:not(.field-weight) .addon input[type=text]{border-width:1px}.attributes-edit-form .field:not(.field-weight) .addon .addafter{display:block;border:0;height:auto;width:auto}.attributes-edit-form .field:not(.field-weight) .addon input:focus~.addafter{box-shadow:none}.attributes-edit-form .with-addon .textarea{margin:0}.attributes-edit-form .attribute-change-checkbox{display:block;margin-top:5px}.attributes-edit-form .attribute-change-checkbox .label{float:none;padding:0;width:auto}.attributes-edit-form .attribute-change-checkbox .checkbox{margin-right:5px;width:auto}.attributes-edit-form .field-price .addon>input,.attributes-edit-form .field-special_price .addon>input,.attributes-edit-form .field-gift_wrapping_price .addon>input,.attributes-edit-form .field-msrp .addon>input,.attributes-edit-form .field-gift_wrapping_price .addon>input{padding-left:23px}.attributes-edit-form .field-price .addafter>strong,.attributes-edit-form .field-special_price .addafter>strong,.attributes-edit-form .field-gift_wrapping_price .addafter>strong,.attributes-edit-form .field-msrp .addafter>strong,.attributes-edit-form .field-gift_wrapping_price .addafter>strong{left:5px;position:absolute;top:3px}.attributes-edit-form .field.type-price input:focus+label,.attributes-edit-form .field-price input:focus+label,.attributes-edit-form .field-special_price input:focus+label,.attributes-edit-form .field-msrp input:focus+label,.attributes-edit-form .field-weight input:focus+label{box-shadow:none}.attributes-edit-form .field-special_from_date>.control .input-text,.attributes-edit-form .field-special_to_date>.control .input-text,.attributes-edit-form .field-news_from_date>.control .input-text,.attributes-edit-form .field-news_to_date>.control .input-text,.attributes-edit-form .field-custom_design_from>.control .input-text,.attributes-edit-form .field-custom_design_to>.control .input-text{border-width:1px;width:130px}.attributes-edit-form .field-weight .fields-group-2 .control{padding-right:27px}.attributes-edit-form .field-weight .fields-group-2 .control .addafter+.addafter{border-width:1px 1px 1px 0;border-style:solid;height:28px;right:0;position:absolute;top:0}.attributes-edit-form .field-weight .fields-group-2 .control .addafter strong{line-height:28px}.attributes-edit-form .field-weight .fields-group-2 .control>input:focus+.addafter+.addafter{box-shadow:0 0 8px rgba(82,168,236,.6)}.attributes-edit-form .field-gift_message_available .addon>input[type=checkbox],.attributes-edit-form .field-gift_wrapping_available .addon>input[type=checkbox]{width:auto;margin-right:5px}.attributes-edit-form .fieldset>.addafter{display:none}.advanced-inventory-edit .field.choice{display:block;margin:3px 0 0}.advanced-inventory-edit .field.choice .label{padding-top:1px}.product-actions:before,.product-actions:after{content:"";display:table}.product-actions:after{clear:both}.product-actions:before,.product-actions:after{content:"";display:table}.product-actions:after{clear:both}.product-actions .switcher{float:right}#configurable-attributes-container .actions-select{display:inline-block;position:relative}#configurable-attributes-container .actions-select:before,#configurable-attributes-container .actions-select:after{content:"";display:table}#configurable-attributes-container .actions-select:after{clear:both}#configurable-attributes-container .actions-select:before,#configurable-attributes-container .actions-select:after{content:"";display:table}#configurable-attributes-container .actions-select:after{clear:both}#configurable-attributes-container .actions-select .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}#configurable-attributes-container .actions-select .action.toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:22px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#configurable-attributes-container .actions-select .action.toggle:hover:after{color:inherit}#configurable-attributes-container .actions-select .action.toggle:active:after{color:inherit}#configurable-attributes-container .actions-select .action.toggle.active{display:inline-block;text-decoration:none}#configurable-attributes-container .actions-select .action.toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:22px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#configurable-attributes-container .actions-select .action.toggle.active:hover:after{color:inherit}#configurable-attributes-container .actions-select .action.toggle.active:active:after{color:inherit}#configurable-attributes-container .actions-select ul.dropdown{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px solid #bbb;position:absolute;z-index:100;top:100%;min-width:100%;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}#configurable-attributes-container .actions-select ul.dropdown li{margin:0;padding:3px 5px}#configurable-attributes-container .actions-select ul.dropdown li:hover{background:#e8e8e8;cursor:pointer}#configurable-attributes-container .actions-select.active{overflow:visible}#configurable-attributes-container .actions-select.active ul.dropdown{display:block}#configurable-attributes-container .actions-select .action.toggle{padding:1px 8px;border:1px solid #ada89e;background:#fff;border-radius:0 2px 2px 0}#configurable-attributes-container .actions-select .action.toggle:after{width:14px;text-indent:-2px}#configurable-attributes-container .actions-select ul.dropdown li:hover{background:#eef8fc}#configurable-attributes-container .actions-select ul.dropdown a{color:#333;text-decoration:none}#product-variations-matrix .actions-image-uploader{display:inline-block;position:relative;display:block;width:50px}#product-variations-matrix .actions-image-uploader:before,#product-variations-matrix .actions-image-uploader:after{content:"";display:table}#product-variations-matrix .actions-image-uploader:after{clear:both}#product-variations-matrix .actions-image-uploader:before,#product-variations-matrix .actions-image-uploader:after{content:"";display:table}#product-variations-matrix .actions-image-uploader:after{clear:both}#product-variations-matrix .actions-image-uploader .action.split{float:left;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle{float:right;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle{padding:6px 5px;display:inline-block;text-decoration:none}#product-variations-matrix .actions-image-uploader .action.toggle>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle:hover:after{color:inherit}#product-variations-matrix .actions-image-uploader .action.toggle:active:after{color:inherit}#product-variations-matrix .actions-image-uploader .action.toggle.active{display:inline-block;text-decoration:none}#product-variations-matrix .actions-image-uploader .action.toggle.active>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:active,#product-variations-matrix .actions-image-uploader .action.toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}#product-variations-matrix .actions-image-uploader .action.toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}#product-variations-matrix .actions-image-uploader .action.toggle.active:hover:after{color:inherit}#product-variations-matrix .actions-image-uploader .action.toggle.active:active:after{color:inherit}#product-variations-matrix .actions-image-uploader ul.dropdown{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px solid #bbb;position:absolute;z-index:100;top:100%;min-width:100%;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}#product-variations-matrix .actions-image-uploader ul.dropdown li{margin:0;padding:3px 5px}#product-variations-matrix .actions-image-uploader ul.dropdown li:hover{background:#e8e8e8;cursor:pointer}#product-variations-matrix .actions-image-uploader.active{overflow:visible}#product-variations-matrix .actions-image-uploader.active ul.dropdown{display:block}#product-variations-matrix .actions-image-uploader .action.toggle{padding:0 2px;border:1px solid #b7b2a7;background:#fff;border-radius:0 4px 4px 0;border-left:none;height:33px}#product-variations-matrix .actions-image-uploader .action.toggle.no-display{display:none}#product-variations-matrix .actions-image-uploader .action.toggle:after{width:12px;text-indent:-5px}#product-variations-matrix .actions-image-uploader ul.dropdown{left:0;margin-left:0;width:100px}#product-variations-matrix .actions-image-uploader ul.dropdown li:hover{background:#eef8fc}#product-variations-matrix .actions-image-uploader ul.dropdown a{color:#333;text-decoration:none}.debugging-hints .page-actions{position:relative;z-index:1}.debugging-hints .page-actions .debugging-hint-template-file{left:auto !important;right:0 !important}.filter-segments{list-style:none;padding:0}.adminhtml-report-customer-test-detail .col-id{width:35px}.adminhtml-report-customer-test-detail .col-period{white-space:nowrap;width:70px}.adminhtml-report-customer-test-detail .col-zip{width:50px}.adminhtml-report-customer-test-segment .col-id{width:35px}.adminhtml-report-customer-test-segment .col-status{width:65px}.adminhtml-report-customer-test-segment .col-qty{width:145px}.adminhtml-report-customer-test-segment .col-segment,.adminhtml-report-customer-test-segment .col-website{width:35%}.adminhtml-report-customer-test-segment .col-select{width:45px}.test-custom-attributes{margin-bottom:20px}.adminhtml-test-index th.col-id{text-align:left}.adminhtml-test-index .col-price{text-align:right;width:50px}.adminhtml-test-index .col-actions{width:50px}.adminhtml-test-index .col-select{width:60px}.adminhtml-test-edit .field-image .control{line-height:28px}.adminhtml-test-edit .field-image a{display:inline-block;margin:0 5px 0 0}.adminhtml-test-edit .field-image img{vertical-align:middle}.adminhtml-test-new .field-image .input-file,.adminhtml-test-edit .field-image .input-file{display:inline-block;margin:0 15px 0 0;width:auto}.adminhtml-test-new .field-image .addafter,.adminhtml-test-edit .field-image .addafter{border:0;box-shadow:none;display:inline-block;margin:0 15px 0 0;height:auto;width:auto}.adminhtml-test-new .field-image .delete-image,.adminhtml-test-edit .field-image .delete-image{display:inline-block;white-space:nowrap}.adminhtml-test-edit .field-image .delete-image input{margin:-3px 5px 0 0;width:auto;display:inline-block}.adminhtml-test-edit .field-image .addon .delete-image input:focus+label{border:0;box-shadow:none}.adminhtml-test-index .col-id{width:35px}.adminhtml-test-index .col-status{white-space:normal;width:75px}.adminhtml-test-index .col-websites{white-space:nowrap;width:200px}.adminhtml-test-index .col-price .label{display:inline-block;min-width:60px;white-space:nowrap}.adminhtml-test-index .col-price .price-excl-tax .price,.adminhtml-test-index .col-price .price-incl-tax .price{font-weight:700}.invitee_information,.inviter_information{width:48.9362%}.invitee_information{float:left}.inviter_information{float:right}.test_information .data-table th,.invitee_information .data-table th,.inviter_information .data-table th{width:20%;white-space:nowrap}.test_information .data-table textarea,.test_information .data-table input{width:100%}.tests-history ul{margin:0;padding-left:25px}.tests-history ul .status:before{display:inline-block;content:"|";margin:0 10px}.adminhtml-report-test-order .col-period{white-space:nowrap;width:70px}.adminhtml-report-test-order .col-inv-sent,.adminhtml-report-test-order .col-inv-acc,.adminhtml-report-test-order .col-acc,.adminhtml-report-test-order .col-rate{text-align:right;width:23%}.adminhtml-report-test-customer .col-id{width:35px}.adminhtml-report-test-customer .col-period{white-space:nowrap;width:70px}.adminhtml-report-test-customer .col-inv-sent,.adminhtml-report-test-customer .col-inv-acc{text-align:right;width:120px}.adminhtml-report-test-index .col-period{white-space:nowrap}.adminhtml-report-test-index .col-inv-sent,.adminhtml-report-test-index .col-inv-acc,.adminhtml-report-test-index .col-inv-disc,.adminhtml-report-test-index .col-inv-acc-rate,.adminhtml-report-test-index .col-inv-disc-rate{text-align:right;width:19%}.test_information .data-table,.invitee_information .data-table,.inviter_information .data-table{width:100%}.test_information .data-table tbody tr th,.invitee_information .data-table tbody tr th,.inviter_information .data-table tbody tr th{font-weight:700}.test_information .data-table tbody tr td,.test_information .data-table tbody tr th,.invitee_information .data-table tbody tr td,.invitee_information .data-table tbody tr th,.inviter_information .data-table tbody tr td,.inviter_information .data-table tbody tr th{background-color:#fff;border:0;padding:9px 10px 10px;color:#666;vertical-align:top}.test_information .data-table tbody tr:nth-child(2n+1) td,.test_information .data-table tbody tr:nth-child(2n+1) th,.invitee_information .data-table tbody tr:nth-child(2n+1) td,.invitee_information .data-table tbody tr:nth-child(2n+1) th,.inviter_information .data-table tbody tr:nth-child(2n+1) td,.inviter_information .data-table tbody tr:nth-child(2n+1) th{background-color:#fbfaf6}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table .col-sort-order{width:80px}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table td,[class^=" adminhtml-test-"] .fieldset-wrapper-content .accordion .config .data-table td{vertical-align:top}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table td select,[class^=" adminhtml-test-"] .fieldset-wrapper-content .accordion .config .data-table td select{display:block;width:100%}[class^=" adminhtml-test-"] .fieldset-wrapper-content .data-table td .input-radio.global-scope,[class^=" adminhtml-test-"] .fieldset-wrapper-content .accordion .config .data-table td .input-radio.global-scope{margin-top:9px}.sales-order-create-index .ui-dialog .content>.test .field.text .input-text{width:100%}.sales-order-create-index .ui-dialog .content>.test .note .price{font-weight:600}.sales-order-create-index .ui-dialog .content>.test .note .price:before{content:": "}.sales-order-create-index .ui-dialog .content>.test .fixed.amount .label:after{content:": "}.sales-order-create-index .ui-dialog .content>.test .fixed.amount .control{display:inline-block;font-weight:600}.sales-order-create-index .ui-dialog .content>.test .fixed.amount .control .control-value{margin:-2px 0 0;padding:0}.eq-ie9 [class^=" adminhtml-test-"] .custom-options .data-table{word-wrap:normal;table-layout:auto}.rma-items .col-actions a.disabled,.newRma .col-actions a.disabled{cursor:default;opacity:.5}.rma-items .col-actions a.disabled:hover,.newRma .col-actions a.disabled:hover{text-decoration:none}.block.mselect-list .mselect-input{width:100%}.block.mselect-list .mselect-input-container .mselect-save{top:4px}.block.mselect-list .mselect-input-container .mselect-cancel{top:4px}html{font-size:62.5%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;font-size-adjust:100%}body,html{height:100%;min-height:100%}body{color:#676056;font-family:'Open Sans',sans-serif;line-height:1.33;font-weight:400;font-size:1.4rem;background:#f2ebde;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}body>*{-webkit-flex-grow:0;flex-grow:0;-webkit-flex-shrink:0;flex-shrink:0;-webkit-flex-basis:auto;flex-basis:auto}.page-wrapper{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-height:100%;width:100%;max-width:100%;min-width:990px}.page-wrapper>*{-webkit-flex-grow:0;flex-grow:0;-webkit-flex-shrink:0;flex-shrink:0;-webkit-flex-basis:auto;flex-basis:auto}.page-header{text-align:right}.page-header-wrapper{background-color:#31302b}.page-header:after{content:"";display:table;clear:both}.page-header .logo{margin-top:5px;float:left;text-decoration:none;display:inline-block}.page-header .logo:before{content:"";display:inline-block;vertical-align:middle;width:109px;height:35px;background-image:url("../images/logo.svg");background-size:109px 70px;background-repeat:no-repeat}.page-header .logo:after{display:inline-block;vertical-align:middle;margin-left:10px;content:attr(data-edition);font-weight:600;font-size:16px;color:#ef672f;margin-top:-2px}.page-header .logo span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.page-header .logo span.focusable:active,.page-header .logo span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.page-header .logo span.focusable:active,.page-header .logo span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.page-header .dropdown-menu{border:0}.admin-user{display:inline-block;vertical-align:top;position:relative;text-align:left}.admin-user-account{text-decoration:none;display:inline-block;padding:12px 14px;color:#f2ebde}.admin-user-account:after{font-family:"MUI-Icons";content:"\e02c";font-size:13px;line-height:13px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:-3px 0 0}.admin-user-account:link,.admin-user-account:visited{color:#f2ebde}.admin-user-account:focus,.admin-user-account:active,.admin-user-account:hover{color:#f2ebde;text-decoration:none}.active .admin-user-account{background-color:#fff;color:#676056}.admin-user-menu{padding:15px;white-space:nowrap;margin-top:0}.admin-user-menu li{border:0;padding:0}.admin-user-menu li:hover{background:0 0}.admin-user-menu a{display:block;color:#676056;font-size:13px;font-weight:400;line-height:1.385;padding:3px 12px 3px;text-decoration:none}.admin-user-menu a:focus,.admin-user-menu a:hover{text-decoration:underline}.admin-user-menu a:hover{color:#fff;background:#989287;text-decoration:none}.admin-user-menu a span:before{content:"("}.admin-user-menu a span:after{content:")"}.page-actions.fixed .page-actions-buttons{padding-right:15px}.page-main-actions{background:#e0dace;color:#645d53;padding:15px;margin-left:auto;margin-right:auto;box-sizing:border-box}.page-main-actions:before,.page-main-actions:after{content:"";display:table}.page-main-actions:after{clear:both}.page-main-actions:before,.page-main-actions:after{content:"";display:table}.page-main-actions:after{clear:both}.page-main-actions .page-actions{float:right}.page-main-actions .page-actions .page-actions-buttons{float:right;display:-webkit-flex;display:-ms-flexbox;display:flex;justify-content:flex-end}.page-main-actions .page-actions button,.page-main-actions .page-actions .action-add.mselect-button-add{margin-left:13px}.page-main-actions .page-actions button.primary,.page-main-actions .page-actions .action-add.mselect-button-add.primary{float:right;-ms-flex-order:2;-webkit-order:2;order:2}.page-main-actions .page-actions button.save:not(.primary),.page-main-actions .page-actions .action-add.mselect-button-add.save:not(.primary){float:right;-ms-flex-order:1;-webkit-order:1;order:1}.page-main-actions .page-actions button.back,.page-main-actions .page-actions button.action-back,.page-main-actions .page-actions button.delete,.page-main-actions .page-actions .action-add.mselect-button-add.back,.page-main-actions .page-actions .action-add.mselect-button-add.action-back,.page-main-actions .page-actions .action-add.mselect-button-add.delete{background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400;margin:0 13px}.page-main-actions .page-actions button.back:focus,.page-main-actions .page-actions button.action-back:focus,.page-main-actions .page-actions button.delete:focus,.page-main-actions .page-actions button.back:active,.page-main-actions .page-actions button.action-back:active,.page-main-actions .page-actions button.delete:active,.page-main-actions .page-actions .action-add.mselect-button-add.back:focus,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:focus,.page-main-actions .page-actions .action-add.mselect-button-add.delete:focus,.page-main-actions .page-actions .action-add.mselect-button-add.back:active,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:active,.page-main-actions .page-actions .action-add.mselect-button-add.delete:active{background:0 0;border:none}.page-main-actions .page-actions button.back:hover,.page-main-actions .page-actions button.action-back:hover,.page-main-actions .page-actions button.delete:hover,.page-main-actions .page-actions .action-add.mselect-button-add.back:hover,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:hover,.page-main-actions .page-actions .action-add.mselect-button-add.delete:hover{background:0 0;border:none}.page-main-actions .page-actions button.back.disabled,.page-main-actions .page-actions button.action-back.disabled,.page-main-actions .page-actions button.delete.disabled,.page-main-actions .page-actions button.back[disabled],.page-main-actions .page-actions button.action-back[disabled],.page-main-actions .page-actions button.delete[disabled],fieldset[disabled] .page-main-actions .page-actions button.back,fieldset[disabled] .page-main-actions .page-actions button.action-back,fieldset[disabled] .page-main-actions .page-actions button.delete,.page-main-actions .page-actions .action-add.mselect-button-add.back.disabled,.page-main-actions .page-actions .action-add.mselect-button-add.action-back.disabled,.page-main-actions .page-actions .action-add.mselect-button-add.delete.disabled,.page-main-actions .page-actions .action-add.mselect-button-add.back[disabled],.page-main-actions .page-actions .action-add.mselect-button-add.action-back[disabled],.page-main-actions .page-actions .action-add.mselect-button-add.delete[disabled],fieldset[disabled] .page-main-actions .page-actions .action-add.mselect-button-add.back,fieldset[disabled] .page-main-actions .page-actions .action-add.mselect-button-add.action-back,fieldset[disabled] .page-main-actions .page-actions .action-add.mselect-button-add.delete{cursor:not-allowed;pointer-events:none;opacity:.5}.ie .page-main-actions .page-actions button.back,.ie .page-main-actions .page-actions button.action-back,.ie .page-main-actions .page-actions button.delete,.ie .page-main-actions .page-actions .action-add.mselect-button-add.back,.ie .page-main-actions .page-actions .action-add.mselect-button-add.action-back,.ie .page-main-actions .page-actions .action-add.mselect-button-add.delete{margin-top:6px}.page-main-actions .page-actions button.delete,.page-main-actions .page-actions .action-add.mselect-button-add.delete{color:#e22626;float:left;-ms-flex-order:-1;-webkit-order:-1;order:-1}.page-main-actions .page-actions button.back,.page-main-actions .page-actions button.action-back,.page-main-actions .page-actions .action-add.mselect-button-add.back,.page-main-actions .page-actions .action-add.mselect-button-add.action-back{float:left;-ms-flex-order:-1;-webkit-order:-1;order:-1;display:inline-block;text-decoration:none}.page-main-actions .page-actions button.back:before,.page-main-actions .page-actions button.action-back:before,.page-main-actions .page-actions .action-add.mselect-button-add.back:before,.page-main-actions .page-actions .action-add.mselect-button-add.action-back:before{font-family:'icons-blank-theme';content:'\e625';font-size:inherit;line-height:normal;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:0 2px 0 0}.page-main-actions .page-actions .actions-split{margin-left:13px;float:right;-ms-flex-order:2;-webkit-order:2;order:2}.page-main-actions .page-actions .actions-split button.primary,.page-main-actions .page-actions .actions-split .action-add.mselect-button-add.primary{float:left}.page-main-actions .page-actions .actions-split .dropdown-menu{text-align:left}.page-main-actions .page-actions .actions-split .dropdown-menu .item{display:block}.page-main-actions .page-actions.fixed{position:fixed;top:0;left:0;right:0;z-index:10;padding:0;background:-webkit-linear-gradient(top,#f5f2ed 0%,#f5f2ed 56%,rgba(245,242,237,0) 100%);background:-ms-linear-gradient(top,#f5f2ed 0%,#f5f2ed 56%,rgba(245,242,237,0) 100%);background:linear-gradient(to bottom,#f5f2ed 0%,#f5f2ed 56%,rgba(245,242,237,0) 100%);background:#e0dace}.page-main-actions .page-actions.fixed .page-actions-inner{position:relative;padding-top:15px;padding-bottom:15px;min-height:36px;text-align:right;box-sizing:border-box}.page-main-actions .page-actions.fixed .page-actions-inner:before,.page-main-actions .page-actions.fixed .page-actions-inner:after{content:"";display:table}.page-main-actions .page-actions.fixed .page-actions-inner:after{clear:both}.page-main-actions .page-actions.fixed .page-actions-inner:before,.page-main-actions .page-actions.fixed .page-actions-inner:after{content:"";display:table}.page-main-actions .page-actions.fixed .page-actions-inner:after{clear:both}.page-main-actions .page-actions.fixed .page-actions-inner:before{text-align:left;content:attr(data-title);float:left;font-size:20px;max-width:50%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.lt-ie10 .page-main-actions .page-actions.fixed .page-actions-inner{background:#f5f2ed}.page-main-actions .store-switcher{margin-top:5px}.store-switcher{display:inline-block;font-size:13px}.store-switcher .label{margin-right:5px}.store-switcher .actions.dropdown{display:inline-block;position:relative}.store-switcher .actions.dropdown:before,.store-switcher .actions.dropdown:after{content:"";display:table}.store-switcher .actions.dropdown:after{clear:both}.store-switcher .actions.dropdown:before,.store-switcher .actions.dropdown:after{content:"";display:table}.store-switcher .actions.dropdown:after{clear:both}.store-switcher .actions.dropdown .action.toggle{cursor:pointer;display:inline-block;text-decoration:none}.store-switcher .actions.dropdown .action.toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:20px;color:#645d53;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.store-switcher .actions.dropdown .action.toggle:hover:after{color:#645d53}.store-switcher .actions.dropdown .action.toggle:active:after{color:#645d53}.store-switcher .actions.dropdown .action.toggle.active{display:inline-block;text-decoration:none}.store-switcher .actions.dropdown .action.toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:20px;color:#645d53;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.store-switcher .actions.dropdown .action.toggle.active:hover:after{color:#645d53}.store-switcher .actions.dropdown .action.toggle.active:active:after{color:#645d53}.store-switcher .actions.dropdown .dropdown-menu{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px #ada89e solid;position:absolute;z-index:100;top:100%;min-width:195px;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}.store-switcher .actions.dropdown .dropdown-menu li{margin:0;padding:0}.store-switcher .actions.dropdown .dropdown-menu li:hover{background:0 0;cursor:pointer}.store-switcher .actions.dropdown.active{overflow:visible}.store-switcher .actions.dropdown.active .dropdown-menu{display:block}.store-switcher .actions.dropdown .action.toggle{background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400;color:#026294;line-height:normal;margin-top:2px;vertical-align:middle}.store-switcher .actions.dropdown .action.toggle:focus,.store-switcher .actions.dropdown .action.toggle:active{background:0 0;border:none}.store-switcher .actions.dropdown .action.toggle:hover{background:0 0;border:none}.store-switcher .actions.dropdown .action.toggle.disabled,.store-switcher .actions.dropdown .action.toggle[disabled],fieldset[disabled] .store-switcher .actions.dropdown .action.toggle{cursor:not-allowed;pointer-events:none;opacity:.5}.store-switcher .actions.dropdown ul.dropdown-menu{margin-top:4px;padding-top:5px;left:0}.store-switcher .actions.dropdown ul.dropdown-menu li{border:0;cursor:default}.store-switcher .actions.dropdown ul.dropdown-menu li:hover{cursor:default}.store-switcher .actions.dropdown ul.dropdown-menu li a,.store-switcher .actions.dropdown ul.dropdown-menu li span{padding:5px 13px;display:block;color:#645d53}.store-switcher .actions.dropdown ul.dropdown-menu li a{text-decoration:none}.store-switcher .actions.dropdown ul.dropdown-menu li a:hover{background:#edf9fb}.store-switcher .actions.dropdown ul.dropdown-menu li span{color:#ababab;cursor:default}.store-switcher .actions.dropdown ul.dropdown-menu li.current span{color:#645d53;background:#eee}.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store a,.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store span{padding-left:26px}.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store-view a,.store-switcher .actions.dropdown ul.dropdown-menu .store-switcher-store-view span{padding-left:39px}.store-switcher .actions.dropdown ul.dropdown-menu .dropdown-toolbar{border-top:1px #ededed solid;margin-top:10px}.store-switcher .actions.dropdown ul.dropdown-menu .dropdown-toolbar a{display:inline-block;text-decoration:none;display:block}.store-switcher .actions.dropdown ul.dropdown-menu .dropdown-toolbar a:before{font-family:'icons-blank-theme';content:'\e606';font-size:20px;line-height:normal;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:text-top;text-align:center;margin:0 3px 0 -4px}.tooltip{display:inline-block;margin-left:5px}.tooltip .help span,.tooltip .help a{width:16px;height:16px;text-align:center;background:rgba(194,186,169,.5);cursor:pointer;border-radius:10px;vertical-align:middle;display:inline-block;text-decoration:none}.tooltip .help span:hover,.tooltip .help a:hover{background:#c2baa9}.tooltip .help span>span,.tooltip .help a>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.tooltip .help span>span.focusable:active,.tooltip .help a>span.focusable:active,.tooltip .help span>span.focusable:focus,.tooltip .help a>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.tooltip .help span>span.focusable:active,.tooltip .help a>span.focusable:active,.tooltip .help span>span.focusable:focus,.tooltip .help a>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.tooltip .help span:before,.tooltip .help a:before{font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;content:'?';font-size:13px;line-height:16px;color:#5a534a;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center}.tooltip .help span:before,.tooltip .help a:before{font-weight:700}.tooltip .tooltip-content{display:none;position:absolute;max-width:200px;margin-top:10px;margin-left:-19px;padding:4px 8px;border-radius:3px;background:#000;background:rgba(49,48,43,.8);color:#fff;text-shadow:none;z-index:20}.tooltip .tooltip-content:before{content:'';position:absolute;width:0;height:0;top:-5px;left:20px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000;opacity:.8}.tooltip .tooltip-content.loading{position:absolute}.tooltip .tooltip-content.loading:before{border-bottom-color:rgba(0,0,0,.3)}.tooltip:hover>.tooltip-content{display:block}button,.action-add.mselect-button-add{border-radius:2px;background-image:none;background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:0;vertical-align:middle}button:focus,button:active,.action-add.mselect-button-add:focus,.action-add.mselect-button-add:active{background:#cac3b4;border:1px solid #989287}button:hover,.action-add.mselect-button-add:hover{background:#cac3b4}button.disabled,button[disabled],fieldset[disabled] button,.action-add.mselect-button-add.disabled,.action-add.mselect-button-add[disabled],fieldset[disabled] .action-add.mselect-button-add{cursor:default;pointer-events:none;opacity:.5}button.primary,.action-add.mselect-button-add.primary{background-image:none;background:#007dbd;padding:6px 13px;color:#fff;border:1px solid #0a6c9f;cursor:pointer;display:inline-block;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;box-sizing:border-box;vertical-align:middle}button.primary:focus,button.primary:active,.action-add.mselect-button-add.primary:focus,.action-add.mselect-button-add.primary:active{background:#026294;border:1px solid #004c74;color:#fff}button.primary:hover,.action-add.mselect-button-add.primary:hover{background:#026294;border:1px solid #026294}button.primary.disabled,button.primary[disabled],fieldset[disabled] button.primary,.action-add.mselect-button-add.primary.disabled,.action-add.mselect-button-add.primary[disabled],fieldset[disabled] .action-add.mselect-button-add.primary{cursor:default;pointer-events:none;opacity:.5}.actions-split{display:inline-block;position:relative;vertical-align:middle}.actions-split button,.actions-split .action-add.mselect-button-add{margin-left:0!important}.actions-split:before,.actions-split:after{content:"";display:table}.actions-split:after{clear:both}.actions-split:before,.actions-split:after{content:"";display:table}.actions-split:after{clear:both}.actions-split .action-default{float:left;margin:0}.actions-split .action-toggle{float:right;margin:0}.actions-split button.action-default,.actions-split .action-add.mselect-button-add.action-default{border-top-right-radius:0;border-bottom-right-radius:0}.actions-split button+.action-toggle,.actions-split .action-add.mselect-button-add+.action-toggle{border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.actions-split .action-toggle{padding:6px 5px;display:inline-block;text-decoration:none}.actions-split .action-toggle>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.actions-split .action-toggle>span.focusable:active,.actions-split .action-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle>span.focusable:active,.actions-split .action-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.actions-split .action-toggle:hover:after{color:inherit}.actions-split .action-toggle:active:after{color:inherit}.actions-split .action-toggle.active{display:inline-block;text-decoration:none}.actions-split .action-toggle.active>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.actions-split .action-toggle.active>span.focusable:active,.actions-split .action-toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle.active>span.focusable:active,.actions-split .action-toggle.active>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.actions-split .action-toggle.active:after{font-family:'icons-blank-theme';content:'\e618';font-size:22px;line-height:14px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.actions-split .action-toggle.active:hover:after{color:inherit}.actions-split .action-toggle.active:active:after{color:inherit}.actions-split .dropdown-menu{margin:0;padding:0;list-style:none none;box-sizing:border-box;background:#fff;border:1px solid #bbb;position:absolute;z-index:100;top:100%;min-width:175px;margin-top:4px;display:none;box-shadow:0 3px 3px rgba(0,0,0,.15)}.actions-split .dropdown-menu li{margin:0;padding:3px 5px}.actions-split .dropdown-menu li:hover{background:#e8e8e8;cursor:pointer}.actions-split .dropdown-menu:before,.actions-split .dropdown-menu:after{content:"";position:absolute;display:block;width:0;height:0;border-bottom-style:solid}.actions-split .dropdown-menu:before{z-index:99;border:solid 6px;border-color:transparent transparent #fff}.actions-split .dropdown-menu:after{z-index:98;border:solid 7px;border-color:transparent transparent #bbb}.actions-split .dropdown-menu:before{top:-12px;right:10px}.actions-split .dropdown-menu:after{top:-14px;right:9px}.actions-split.active{overflow:visible}.actions-split.active .dropdown-menu{display:block}.actions-split .action-toggle:after{height:13px}.page-content:after{content:"";display:table;clear:both}.page-wrapper>.page-content{margin-bottom:20px}.page-footer{padding:15px 0}.page-footer-wrapper{background-color:#e0dacf;margin-top:auto}.page-footer:after{content:"";display:table;clear:both}.footer-legal{float:right;width:550px}.footer-legal .link-report,.footer-legal .magento-version,.footer-legal .copyright{font-size:13px}.footer-legal:before{content:"";display:inline-block;vertical-align:middle;position:absolute;z-index:1;margin-top:2px;margin-left:-35px;width:30px;height:35px;background-size:109px 70px;background:url("../images/logo.svg") no-repeat 0 -21px}.icon-error{margin-left:15px;color:#c00815;font-size:11px}.icon-error:before{font-family:'MUI-Icons';content:"\e086";font-size:13px;line-height:13px;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center;margin:-1px 5px 0 0}.ui-widget-overlay{position:fixed}.control .nested{padding:0}.control *:first-child{margin-bottom:0}.field-tooltip{display:inline-block;vertical-align:top;margin-top:5px;position:relative;z-index:1;width:0;overflow:visible}.field-choice .field-tooltip{margin-top:10px}.field-tooltip:hover{z-index:99}.field-tooltip-action{position:relative;z-index:2;margin-left:18px;width:22px;height:22px;display:inline-block;cursor:pointer}.field-tooltip-action:before{content:"?";font-weight:500;font-size:18px;display:inline-block;overflow:hidden;height:22px;border-radius:11px;line-height:22px;width:22px;text-align:center;color:#fff;background-color:#514943}.field-tooltip-action span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.field-tooltip-action span.focusable:active,.field-tooltip-action span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.field-tooltip-action span.focusable:active,.field-tooltip-action span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.control-text:focus+.field-tooltip-content,.field-tooltip:hover .field-tooltip-content{display:block}.field-tooltip-content{display:none;position:absolute;z-index:1;width:320px;background:#fff8d7;padding:15px 25px;right:-66px;border:1px solid #adadad;border-radius:1px;bottom:42px;box-shadow:0 2px 8px 0 rgba(0,0,0,.3)}.field-tooltip-content:after,.field-tooltip-content:before{content:"";display:block;width:0;height:0;border:16px solid transparent;border-top-color:#adadad;position:absolute;right:20px;top:100%;z-index:3}.field-tooltip-content:after{border-top-color:#fff8d7;margin-top:-1px;z-index:4}.form__field.field-error .control [class*=control-]{border-color:#e22626}.form__field.field-error .control [class*=control-]:before{border-color:#e22626}.form__field .mage-error{border:1px solid #e22626;display:block;margin:2px 0 0;padding:6px 10px 10px;background:#fff8d6;color:#555;font-size:12px;font-weight:500;box-sizing:border-box;max-width:380px}.no-flexbox.no-flexboxlegacy .form__field .control-addon+.mage-error{display:inline-block;width:100%}.form__field{position:relative;z-index:1}.form__field:hover{z-index:2}.control .form__field{position:static}.form__field[data-config-scope]:before{content:attr(data-config-scope);display:inline-block;position:absolute;color:gray;right:0;top:6px}.control .form__field[data-config-scope]:nth-child(n+2):before{content:""}.form__field.field-disabled>.label{color:#999}.form__field.field-disabled.field .control [class*=control-][disabled]{background-color:#e9e9e9;opacity:.5;color:#303030;border-color:#adadad}.control-fields .label~.control{width:100%}.form__field{border:0;padding:0}.form__field .note{color:#303030;padding:0;margin:10px 0 0;max-width:380px}.form__field .note:before{display:none}.form__field.form__field{margin-bottom:0}.form__field.form__field+.form__field.form__field{margin-top:15px}.form__field.form__field:not(.choice)~.choice{margin-left:20px;margin-top:5px}.form__field.form__field.choice~.choice{margin-top:9px}.form__field.form__field~.choice:last-child{margin-bottom:8px}.fieldset>.form__field{margin-bottom:30px}.form__field .label{color:#303030}.form__field:not(.choice)>.label{font-size:14px;font-weight:600;width:30%;padding-right:30px;padding-top:0;line-height:33px;white-space:nowrap}.form__field:not(.choice)>.label:before{content:".";visibility:hidden;width:0;margin-left:-7px;overflow:hidden}.form__field:not(.choice)>.label span{white-space:normal;display:inline-block;vertical-align:middle;line-height:1.2}.form__field.required>.label:after{content:"";margin-left:0}.form__field.required>.label span:after{content:"*";color:#eb5202;display:inline;font-weight:500;font-size:16px;margin-top:2px;position:absolute;z-index:1;margin-left:10px}.form__field .control-file{margin-top:6px}.form__field .control-select{line-height:33px}.form__field .control-select:not([multiple]),.form__field .control-text{height:33px;max-width:380px}.form__field .control-addon{max-width:380px}.form__field .control-textarea,.form__field .control-select,.form__field .control-text{border:1px solid #adadad;border-radius:1px;padding:0 10px;color:#303030;background-color:#fff;font-weight:500;font-size:15px;min-width:11em}.form__field .control-textarea:focus,.form__field .control-select:focus,.form__field .control-text:focus{outline:0;border-color:#007bdb;box-shadow:none}.form__field .control-text{line-height:auto}.form__field .control-textarea{padding-top:6px;padding-bottom:6px;line-height:1.18em}.form__field .control-select[multiple],.form__field .control-textarea{width:100%;height:calc(6*1.2em + 14px)}.form__field .control-value{display:inline-block;padding:6px 10px}.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:active,.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:active,.form__field .control-fields .form__field:nth-child(n+2):not(.choice)>.label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field .control-select{padding:0}.form__field .control-select option{box-sizing:border-box;padding:4px 10px;display:block}.form__field .control-select optgroup{font-weight:600;display:block;padding:4px 10px;line-height:33px;list-style:inside;font-style:normal}.form__field .control-range>.form__field:nth-child(2):before{content:"\2014";content:":";display:inline-block;margin-left:-25px;float:left;width:20px;line-height:33px;text-align:center}.form__field.choice{position:relative;z-index:1;padding-top:8px;padding-left:26px;padding-right:0}.form__field.choice .label{font-weight:500;padding:0;display:inline;float:none;line-height:18px}.form__field.choice input{position:absolute;top:8px;margin-top:3px!important}.form__field.choice input[disabled]+.label{opacity:.5;cursor:not-allowed}.control>.form__field.choice{max-width:380px}.control>.form__field.choice:nth-child(1):nth-last-child(2),.control>.form__field.choice:nth-child(2):nth-last-child(1){display:inline-block}.control>.form__field.choice:nth-child(1):nth-last-child(2)+.choice,.control>.form__field.choice:nth-child(2):nth-last-child(1)+.choice{margin-left:41px;margin-top:0}.control>.form__field.choice:nth-child(1):nth-last-child(2)+.choice:before,.control>.form__field.choice:nth-child(2):nth-last-child(1)+.choice:before{content:"";position:absolute;display:inline-block;height:20px;top:8px;left:-20px;width:1px;background:#ccc}.form__field.choice .label{cursor:pointer}.form__field.choice .label:before{content:"";position:absolute;z-index:1;border:1px solid #adadad;width:14px;height:14px;top:10px;left:0;border-radius:2px;background:url("../Magento_Ui/images/choice_bkg.png") no-repeat -100% -100%}.form__field.choice input:focus+.label:before{outline:0;border-color:#007bdb}.form__field.choice .control-radio+.label:before{border-radius:8px}.form__field.choice .control-radio:checked+.label:before{background-position:-26px -1px}.form__field.choice .control-checkbox:checked+.label:before{background-position:-1px -1px}.form__field.choice input{opacity:0}.fieldset>.form__field.choice{margin-left:30%}.form__field .control-after,.form__field .control-before{border:0;color:#858585;font-weight:300;font-size:15px;line-height:33px;display:inline-block;height:33px;box-sizing:border-box;padding:0 3px}.no-flexbox.no-flexboxlegacy .form__field .control-before,.no-flexbox.no-flexboxlegacy .form__field .control-addon{float:left;white-space:nowrap}.form__field .control-addon{display:inline-flex;max-width:380px;width:100%;flex-flow:row nowrap;position:relative;z-index:1}.form__field .control-addon>*{position:relative;z-index:1}.form__field .control-addon .control-text[disabled][type],.form__field .control-addon .control-select[disabled][type],.form__field .control-addon .control-select,.form__field .control-addon .control-text{background:transparent!important;border:0;width:auto;vertical-align:top;order:1;flex:1}.form__field .control-addon .control-text[disabled][type]:focus,.form__field .control-addon .control-select[disabled][type]:focus,.form__field .control-addon .control-select:focus,.form__field .control-addon .control-text:focus{box-shadow:none}.form__field .control-addon .control-text[disabled][type]:focus+label:before,.form__field .control-addon .control-select[disabled][type]:focus+label:before,.form__field .control-addon .control-select:focus+label:before,.form__field .control-addon .control-text:focus+label:before{outline:0;border-color:#007bdb}.form__field .control-addon .control-text[disabled][type]+label,.form__field .control-addon .control-select[disabled][type]+label,.form__field .control-addon .control-select+label,.form__field .control-addon .control-text+label{padding-left:10px;position:static!important;z-index:0}.form__field .control-addon .control-text[disabled][type]+label>*,.form__field .control-addon .control-select[disabled][type]+label>*,.form__field .control-addon .control-select+label>*,.form__field .control-addon .control-text+label>*{vertical-align:top;position:relative;z-index:2}.form__field .control-addon .control-text[disabled][type]+label:before,.form__field .control-addon .control-select[disabled][type]+label:before,.form__field .control-addon .control-select+label:before,.form__field .control-addon .control-text+label:before{box-sizing:border-box;border-radius:1px;border:1px solid #adadad;content:"";display:block;position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;background:#fff}.form__field .control-addon .control-text[disabled][type][disabled]+label:before,.form__field .control-addon .control-select[disabled][type][disabled]+label:before,.form__field .control-addon .control-select[disabled]+label:before,.form__field .control-addon .control-text[disabled]+label:before{opacity:.5;background:#e9e9e9}.form__field .control-after{order:3}.form__field .control-after:last-child{padding-right:10px}.form__field .control-before{order:0}.form__field .control-some{display:flex}.form__field [class*=control-grouped]{display:table;width:100%;table-layout:fixed;box-sizing:border-box}.form__field [class*=control-grouped]>.form__field{display:table-cell;width:50%;vertical-align:top}.form__field [class*=control-grouped]>.form__field>.control{width:100%;float:none}.form__field [class*=control-grouped]>.form__field:nth-child(n+2){padding-left:20px}.form__field [class*=control-grouped]>.form__field:nth-child(n+2):not(.choice) .label{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.form__field [class*=control-grouped]>.form__field:nth-child(n+2):not(.choice) .label.focusable:active,.form__field [class*=control-grouped]>.form__field:nth-child(n+2):not(.choice) .label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field [class*=control-grouped]>.form__field:nth-child(n+2):not(.choice) .label.focusable:active,.form__field [class*=control-grouped]>.form__field:nth-child(n+2):not(.choice) .label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.form__field [required]{box-shadow:none}fieldset.form__field{position:relative}fieldset.form__field [class*=control-grouped]>.form__field:first-child>.label,fieldset.form__field .control-fields>.form__field:first-child>.label{position:absolute;left:0;top:0;opacity:0;cursor:pointer;width:30%}.control-text+.ui-datepicker-trigger{background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;line-height:inherit;font-weight:400;text-decoration:none;margin-left:-40px;display:inline-block}.control-text+.ui-datepicker-trigger img{display:none}.control-text+.ui-datepicker-trigger:focus,.control-text+.ui-datepicker-trigger:active{background:0 0;border:none}.control-text+.ui-datepicker-trigger:hover{background:0 0;border:none}.control-text+.ui-datepicker-trigger.disabled,.control-text+.ui-datepicker-trigger[disabled],fieldset[disabled] .control-text+.ui-datepicker-trigger{cursor:not-allowed;pointer-events:none;opacity:.5}.control-text+.ui-datepicker-trigger>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.control-text+.ui-datepicker-trigger>span.focusable:active,.control-text+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.control-text+.ui-datepicker-trigger>span.focusable:active,.control-text+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.control-text+.ui-datepicker-trigger:after{font-family:'icons-blank-theme';content:'\e612';font-size:38px;line-height:33px;color:#514943;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}[class*=tab-nav-item]:not(ul):active,[class*=tab-nav-item]:not(ul):focus{box-shadow:none;outline:none}.customer-index-edit .col-2-left-layout,.customer-index-edit .col-1-layout{background:#fff}.customer-index-edit{background:#fff}.customer-index-edit .col-2-left-layout{background:#fff}.customer-index-edit .main-col{padding-left:40px}.customer-index-edit .page-main-actions{background:0 0}.tab-nav.block{margin-bottom:40px}.tab-nav.block:first-child{margin-top:16px}.tab-nav.block .block-title{padding:7px 20px}.tab-nav-items{padding:0;border:1px solid #d3d3d3;box-shadow:0 0 4px rgba(50,50,50,.35);margin:0 0 40px;background:#f7f7f7}.tab-nav-item{padding:0;list-style-type:none;border-bottom:1px solid #e0e0e0;position:relative;margin:0 15px;z-index:1}.tab-nav-item:last-child{border-bottom:0}.tab-nav-item.ui-state-active{z-index:2;background:#fff;padding:1px 14px;border:2px solid #eb5202;margin:-1px}.tab-nav-item.ui-state-active .tab-nav-item-link{padding:13px 15px 13px;color:#eb5202}.tab-nav-item.ui-tabs-loading{position:relative;z-index:1}.tab-nav-item.ui-tabs-loading:before{content:"";display:block;position:absolute;z-index:2;background:url("../images/loader-2.gif") no-repeat 50% 50%;background-size:120px;width:20px;height:20px;top:13px;left:-10px}.tab-nav-item.ui-tabs-loading.ui-state-active:before{top:12px;left:4px}.tab-nav-item-link{display:block;padding:15px;color:#666;line-height:1}.tab-nav-item-link:focus,.tab-nav-item-link:active,.tab-nav-item-link:hover{outline:0;color:#eb5202;text-decoration:none}.ui-state-active .tab-nav-item-link{color:#666;font-weight:600}.tab-nav-item-link.changed{font-style:italic}.listing-tiles{overflow:hidden;margin-top:-10px;margin-left:-10px}.listing-tiles .listing-tile{background-color:#f2ebde;display:block;width:238px;height:200px;float:left;border:1px solid #676056;margin-top:10px;margin-left:10px;border-radius:4px;text-align:center}.listing-tiles .listing-tile.disabled{border-color:red}.listing-tiles .listing-tile.enabled{border-color:green}.listing .disabled{color:red}.listing .enabled{color:green}.pager{text-align:left;padding-bottom:10px}.pager:before,.pager:after{content:"";display:table}.pager:after{clear:both}.pager:before,.pager:after{content:"";display:table}.pager:after{clear:both}.pager [data-part=left]{display:inline-block;width:45%;float:left;text-align:left}.pager [data-part=right]{display:inline-block;width:45%;text-align:right;float:right;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.pager .action-next{cursor:pointer}.pager .action-previous{cursor:pointer}.pager{text-align:left}.pager [data-part=left]{display:inline-block;width:45%;text-align:left}.pager [data-part=right]{display:inline-block;width:45%;text-align:right;float:right}.grid .col-title{min-width:90px;text-align:center}.grid-actions [data-part=search]{display:inline-block;margin:0 30px}.grid-actions [data-part=search] input[type=text]{vertical-align:bottom;width:460px}.grid .actions-split .dropdown-menu{right:auto;left:auto;text-align:left;color:#676056;font-weight:400}.grid .actions-split .dropdown-menu:after{right:auto;left:9px}.grid .actions-split .dropdown-menu:before{right:auto;left:10px}.grid .grid-actions{padding:10px 0}.grid .hor-scroll{padding-top:10px}.grid .select-box{display:inline-block;vertical-align:top;margin:-12px -10px -7px;padding:12px 10px 7px;width:100%}.filters-toggle{background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400}.filters-toggle:after{font-family:'icons-blank-theme';content:'\e607';font-size:30px;line-height:15px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.filters-toggle:hover:after{color:inherit}.filters-toggle:active:after{color:inherit}.filters-toggle:focus,.filters-toggle:active{background:#cac3b4;border:1px solid #989287}.filters-toggle:hover{background:#cac3b4}.filters-toggle.disabled,.filters-toggle[disabled],fieldset[disabled] .filters-toggle{cursor:default;pointer-events:none;opacity:.5}.filters-toggle:focus,.filters-toggle:active{background:0 0;border:none}.filters-toggle:hover{background:0 0;border:none}.filters-toggle.disabled,.filters-toggle[disabled],fieldset[disabled] .filters-toggle{cursor:not-allowed;pointer-events:none;opacity:.5}.filters-toggle:after{margin-top:2px;margin-left:-3px}.filters-toggle.active:after{content:'\e618'}.filters-current{padding:10px 0;display:none}.filters-current.active{display:block}.filters-items{margin:0;padding:0;list-style:none none;display:inline}.filters-item{display:inline-block;margin:0 5px 5px 0;padding:2px 2px 2px 4px;border-radius:3px;background:#f7f3eb}.filters-item .item-label{font-weight:600}.filters-item .item-label:after{content:": "}.filters-item .action-remove{background-image:none;background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;text-decoration:none;padding:0}.filters-item .action-remove>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.filters-item .action-remove>span.focusable:active,.filters-item .action-remove>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-item .action-remove>span.focusable:active,.filters-item .action-remove>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-item .action-remove:before{font-family:'icons-blank-theme';content:'\e616';font-size:16px;line-height:16px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.filters-item .action-remove:hover:before{color:inherit}.filters-item .action-remove:active:before{color:inherit}.filters-item .action-remove:focus,.filters-item .action-remove:active{background:#cac3b4;border:1px solid #989287}.filters-item .action-remove:hover{background:#cac3b4}.filters-item .action-remove.disabled,.filters-item .action-remove[disabled],fieldset[disabled] .filters-item .action-remove{cursor:default;pointer-events:none;opacity:.5}.filters-form{position:relative;z-index:1;margin:14px 0;background:#fff;border:1px solid #bbb;box-shadow:0 3px 3px rgba(0,0,0,.15)}.filters-form .action-close{position:absolute;top:3px;right:7px;background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400}.filters-form .action-close>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.filters-form .action-close>span.focusable:active,.filters-form .action-close>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-form .action-close>span.focusable:active,.filters-form .action-close>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters-form .action-close:before{font-family:'icons-blank-theme';content:'\e616';font-size:42px;line-height:42px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.filters-form .action-close:hover:before{color:inherit}.filters-form .action-close:active:before{color:inherit}.filters-form .action-close:focus,.filters-form .action-close:active{background:#cac3b4;border:1px solid #989287}.filters-form .action-close:hover{background:#cac3b4}.filters-form .action-close.disabled,.filters-form .action-close[disabled],fieldset[disabled] .filters-form .action-close{cursor:default;pointer-events:none;opacity:.5}.filters-form .action-close:focus,.filters-form .action-close:active{background:0 0;border:none}.filters-form .action-close:hover{background:0 0;border:none}.filters-form .action-close.disabled,.filters-form .action-close[disabled],fieldset[disabled] .filters-form .action-close{cursor:not-allowed;pointer-events:none;opacity:.5}.filters-actions{margin:18px;text-align:right}.filters-fieldset{padding-bottom:0}.filters-fieldset .field{border:0;margin:0 0 20px;box-sizing:border-box;display:inline-block;padding:0 12px 0 0;width:33.33333333%;vertical-align:top}.filters-fieldset .field:before,.filters-fieldset .field:after{content:"";display:table}.filters-fieldset .field:after{clear:both}.filters-fieldset .field:before,.filters-fieldset .field:after{content:"";display:table}.filters-fieldset .field:after{clear:both}.filters-fieldset .field.choice:before,.filters-fieldset .field.no-label:before{box-sizing:border-box;content:" ";height:1px;float:left;padding:6px 15px 0 0;width:35%}.filters-fieldset .field .description{box-sizing:border-box;float:left;padding:6px 15px 0 0;text-align:right;width:35%}.filters-fieldset .field:not(.choice)>.label{box-sizing:border-box;float:left;padding:6px 15px 0 0;text-align:right;width:35%}.filters-fieldset .field:not(.choice)>.control{float:left;width:65%}.filters-fieldset .field:last-child{margin-bottom:0}.filters-fieldset .field+.fieldset{clear:both}.filters-fieldset .field>.label{font-weight:700}.filters-fieldset .field>.label+br{display:none}.filters-fieldset .field .choice input{vertical-align:top}.filters-fieldset .field .fields.group:before,.filters-fieldset .field .fields.group:after{content:"";display:table}.filters-fieldset .field .fields.group:after{clear:both}.filters-fieldset .field .fields.group:before,.filters-fieldset .field .fields.group:after{content:"";display:table}.filters-fieldset .field .fields.group:after{clear:both}.filters-fieldset .field .fields.group .field{box-sizing:border-box;float:left}.filters-fieldset .field .fields.group.group-2 .field{width:50% !important}.filters-fieldset .field .fields.group.group-3 .field{width:33.3% !important}.filters-fieldset .field .fields.group.group-4 .field{width:25% !important}.filters-fieldset .field .fields.group.group-5 .field{width:20% !important}.filters-fieldset .field .addon{display:-webkit-inline-flex;display:-ms-inline-flexbox;display:inline-flex;-webkit-flex-wrap:nowrap;flex-wrap:nowrap;padding:0;width:100%}.filters-fieldset .field .addon textarea,.filters-fieldset .field .addon select,.filters-fieldset .field .addon input{-ms-flex-order:2;-webkit-order:2;order:2;-webkit-flex-basis:100%;flex-basis:100%;box-shadow:none;display:inline-block;margin:0;width:auto}.filters-fieldset .field .addon .addbefore,.filters-fieldset .field .addon .addafter{-ms-flex-order:3;-webkit-order:3;order:3;display:inline-block;box-sizing:border-box;background:#fff;border:1px solid #c2c2c2;border-radius:1px;height:32px;width:100%;padding:0 9px;font-size:14px;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;line-height:1.428571429;background-clip:padding-box;vertical-align:baseline;width:auto;white-space:nowrap;vertical-align:middle}.filters-fieldset .field .addon .addbefore:disabled,.filters-fieldset .field .addon .addafter:disabled{opacity:.5}.filters-fieldset .field .addon .addbefore::-moz-placeholder,.filters-fieldset .field .addon .addafter::-moz-placeholder{color:#c2c2c2}.filters-fieldset .field .addon .addbefore::-webkit-input-placeholder,.filters-fieldset .field .addon .addafter::-webkit-input-placeholder{color:#c2c2c2}.filters-fieldset .field .addon .addbefore:-ms-input-placeholder,.filters-fieldset .field .addon .addafter:-ms-input-placeholder{color:#c2c2c2}.filters-fieldset .field .addon .addbefore{float:left;-ms-flex-order:1;-webkit-order:1;order:1}.filters-fieldset .field .additional{margin-top:10px}.filters-fieldset .field.required>.label:after{content:'*';font-size:1.2rem;color:#e02b27;margin:0 0 0 5px}.filters-fieldset .field .note{font-size:1.2rem;margin:3px 0 0;padding:0;display:inline-block;text-decoration:none}.filters-fieldset .field .note:before{font-family:'icons-blank-theme';content:'\e618';font-size:24px;line-height:12px;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.filters-fieldset .field .label{color:#676056;font-size:13px;font-weight:600;margin:0}.filters .field-date .group .hasDatepicker{width:100%;padding-right:30px}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger{background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;line-height:inherit;font-weight:400;text-decoration:none;margin-left:-33px;display:inline-block;width:30px}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger img{display:none}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:focus,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:active{background:0 0;border:none}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:hover{background:0 0;border:none}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger.disabled,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger[disabled],fieldset[disabled] .filters .field-date .group .hasDatepicker+.ui-datepicker-trigger{cursor:not-allowed;pointer-events:none;opacity:.5}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:active,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:active,.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.filters .field-date .group .hasDatepicker+.ui-datepicker-trigger:after{font-family:'icons-blank-theme';content:'\e612';font-size:35px;line-height:30px;color:#514943;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:middle;text-align:center}.filters .field-range .group .field{margin-bottom:0}.filters .field-range .group .control{width:100%;box-sizing:border-box;padding-right:0;position:relative;z-index:1}.mass-select{position:relative;margin:-6px -10px;padding:6px 2px 6px 10px;z-index:1;white-space:nowrap}.mass-select.active{background:rgba(0,0,0,.2)}.mass-select-toggle{background:#f2ebde;padding:6px 13px;color:#645d53;border:1px solid #ada89e;cursor:pointer;font-family:'Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;font-size:1.3rem;font-weight:500;line-height:1.4rem;box-sizing:border-box;margin:3px;vertical-align:middle;display:inline-block;background-image:none;background:0 0;border:0;margin:0;padding:0;-moz-box-sizing:content-box;box-shadow:none;text-shadow:none;text-decoration:none;line-height:inherit;font-weight:400}.mass-select-toggle>span{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.mass-select-toggle>span.focusable:active,.mass-select-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.mass-select-toggle>span.focusable:active,.mass-select-toggle>span.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.mass-select-toggle:before{font-family:'icons-blank-theme';content:'\e607';font-size:30px;line-height:15px;color:inherit;overflow:hidden;speak:none;font-weight:400;-webkit-font-smoothing:antialiased;display:inline-block;vertical-align:top;text-align:center;margin:0}.mass-select-toggle:hover:before{color:inherit}.mass-select-toggle:active:before{color:inherit}.mass-select-toggle:focus,.mass-select-toggle:active{background:#cac3b4;border:1px solid #989287}.mass-select-toggle:hover{background:#cac3b4}.mass-select-toggle.disabled,.mass-select-toggle[disabled],fieldset[disabled] .mass-select-toggle{cursor:default;pointer-events:none;opacity:.5}.mass-select-toggle:focus,.mass-select-toggle:active{background:0 0;border:none}.mass-select-toggle:hover{background:0 0;border:none}.mass-select-toggle.disabled,.mass-select-toggle[disabled],fieldset[disabled] .mass-select-toggle{cursor:not-allowed;pointer-events:none;opacity:.5}.mass-select-toggle:before{margin-top:-2px;text-indent:-5px;color:#fff}.mass-select-toggle:hover:before{color:#fff}.mass-select-toggle:active:before,.mass-select-toggle.active:before{content:'\e618'}.mass-select-field{display:inline}.mass-select-menu{display:none;position:absolute;top:100%;left:0;text-align:left;margin:0;padding:0;list-style:none none;background:#fff;border:1px solid #bbb;min-width:175px;box-shadow:0 3px 3px rgba(0,0,0,.15)}.mass-select-menu li{margin:0;padding:4px 15px;border-bottom:1px solid #e5e5e5}.mass-select-menu li:hover{background:#e8e8e8;cursor:pointer}.mass-select-menu span{font-weight:400;font-size:13px;color:#645d53}.mass-select-menu.active{display:block}.grid-loading-mask{position:absolute;left:0;top:0;right:0;bottom:0;background:rgba(255,255,255,.5);z-index:100}.grid-loading-mask .grid-loader{position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;width:218px;height:149px;background:url('../images/loader-2.gif') 50% 50% no-repeat}.addon input{border-width:1px 0 1px 1px}.addon input~.addafter strong{display:inline-block;background:#fff;line-height:24px;margin:0 3px 0 -2px;padding-left:4px;padding-right:4px;position:relative;font-size:12px;top:0}.addon input:focus~.addafter{border-color:#75b9f0;box-shadow:0 0 8px rgba(82,168,236,.6)}.addon input:focus~.addafter strong{margin-top:0}.addon .addafter{background:0 0;color:#a6a6a6;border-width:1px 1px 1px 0;border-radius:2px 2px 0 0;padding:0;border-color:#ada89e}.addon .pager input{border-width:1px}.field .control input[type=text][disabled],.field .control input[type=text][disabled]~.addafter,.field .control select[disabled],.field .control select[disabled]~.addafter{background-color:#fff;border-color:#eee;box-shadow:none;color:#999}.field .control input[type=text][disabled]~.addafter strong,.field .control select[disabled]~.addafter strong{background-color:#fff}.field-price.addon{direction:rtl}.field-price.addon>*{direction:ltr}.field-price.addon .addafter{border-width:1px 0 1px 1px;border-radius:2px 0 0 2px}.field-price.addon input:first-child{border-radius:0 2px 2px 0}.field-price input{border-width:1px 1px 1px 0}.field-price input:focus{box-shadow:0 0 8px rgba(82,168,236,.6)}.field-price input:focus~label.addafter{box-shadow:0 0 8px rgba(82,168,236,.6)}.field-price input~label.addafter strong{margin-left:2px;margin-right:-2px}.field-price.addon>input{width:99px;float:left}.field-price .control{position:relative}.field-price label.mage-error{position:absolute;left:0;top:30px}.version-fieldset .grid-actions{border-bottom:1px solid #f2ebde;margin:0 0 15px;padding:0 0 15px}.navigation>ul,.message-system,.page-header,.page-actions.fixed .page-actions-inner,.page-content,.page-footer{width:auto;min-width:960px;max-width:1300px;margin:0 auto;padding-left:15px;padding-right:15px;box-sizing:border-box;width:100%}.pager label.page,.filters .field-range .group .label,.mass-select-field .label{clip:rect(0,0,0,0);border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visually-hidden.focusable:active,.visually-hidden.focusable:focus,.pager label.page.focusable:active,.pager label.page.focusable:focus,.filters .field-range .group .label.focusable:active,.filters .field-range .group .label.focusable:focus,.mass-select-field .label.focusable:active,.mass-select-field .label.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}table th.required:after,.data-table th.required-entry:after,.data-table td.required-entry:after,.grid-actions .filter.required .label span:after,.grid-actions .required:after,.accordion .config .data-table td.required-entry:after{content:'*';color:#e22626;font-weight:400;margin-left:3px}.grid th.required:after,.grid th .required:after{content:'*';color:#f9d4d4;font-weight:400;margin-left:3px}.grid td.col-period,.grid td.col-date,.grid td.col-date_to,.grid td.col-date_from,.grid td.col-ended_at,.grid td.col-created_at,.grid td.col-updated_at,.grid td.col-customer_since,.grid td.col-session_start_time,.grid td.col-last_activity,.grid td.col-email,.grid td.col-name,.grid td.col-sku,.grid td.col-firstname,.grid td.col-lastname,.grid td.col-title,.grid td.col-label,.grid td.col-product,.grid td.col-set_name,.grid td.col-websites,.grid td.col-time,.grid td.col-billing_name,.grid td.col-shipping_name,.grid td.col-phone,.grid td.col-type,.product-options .grouped-items-table .col-name,.product-options .grouped-items-table .col-sku,.sales-order-create-index .data-table .col-product,[class^=' adminhtml-rma-'] .fieldset-wrapper .data-table td,[class^=' adminhtml-rma-'] .grid .col-product_sku,[class^=' adminhtml-rma-'] .grid .col-product_name,.col-grid_segment_name,.adminhtml-catalog-event-index .col-category,[class^=' catalog-search'] .col-search_query,[class^=' catalog-search'] .col-synonym_for,[class^=' catalog-search'] .col-redirect,.adminhtml-urlrewrite-index .col-request_path,.adminhtml-cms-page-index .col-title,.adminhtml-cms-page-index .col-identifier,.adminhtml-cms-hierarchy-index .col-title,.adminhtml-cms-hierarchy-index .col-identifier,.col-banner_name,.adminhtml-widget-instance-index .col-title,.reports-index-search .col-query_text,.adminhtml-rma-item-attribute-index .grid .col-attr-code,.adminhtml-system-store-index .grid td,.catalog-product-attribute-index .col-attr-code,.catalog-product-attribute-index .col-label,.adminhtml-export-index .col-code,.adminhtml-logging-index .grid .col-fullaction,.adminhtml-system-variable-index .grid .col-code,.adminhtml-logging-index .grid .col-info,.dashboard-secondary .dashboard-item tr>td:first-child,.ui-tabs-panel .dashboard-data .col-name,.data-table-td-max .data-table td,[class^=' adminhtml-rma-'] .fieldset-wrapper .accordion .config .data-table td,.data-table-td-max .accordion .config .data-table td,.order-account-information .data-table td,[class^=' adminhtml-rma-'] .rma-request-details .data-table td{overflow:hidden;text-overflow:ellipsis}td.col-period,td.col-date,td.col-date_to,td.col-date_from,td.col-ended_at,td.col-created_at,td.col-updated_at,td.col-customer_since,td.col-session_start_time,td.col-time,td.col-sku,td.col-type,[class^=' adminhtml-rma-'] #rma_items_grid_table .headings th,.adminhtml-process-list .col-action a,.adminhtml-process-list .col-mode{white-space:nowrap}table thead tr th:first-child,table tfoot tr th:first-child,table tfoot tr td:first-child{border-left:0}table thead tr th:last-child,table tfoot tr th:last-child,table tfoot tr td:last-child{border-right:0}.form-inline .grid-actions .label,.form-inline .massaction .label{padding:0;width:auto}.grid .col-action,.grid .col-actions,.grid .col-qty,.grid .col-purchases,.catalog-product-edit .ui-tabs-panel .grid .col-price,.catalog-product-edit .ui-tabs-panel .grid .col-position{width:50px}.grid .col-order-number,.grid .col-real_order_id,.grid .col-invoice-number,.grid .col-increment_id,.grid .col-transaction-id,.grid .col-parent-transaction-id,.grid .col-reference_id,.grid .col-status,.grid .col-price,.grid .col-position,.grid .col-base_grand_total,.grid .col-grand_total,.grid .col-sort_order,.grid .col-carts,.grid .col-priority,.grid .col-severity,.sales-order-create-index .col-in_products,[class^=' reports-'] [class^=col-total],[class^=' reports-'] [class^=col-average],[class^=' reports-'] [class^=col-ref-],[class^=' reports-'] [class^=col-rate],[class^=' reports-'] [class^=col-tax-amount],[class^=' adminhtml-customer-'] .col-required,.adminhtml-rma-item-attribute-index .col-required,[class^=' adminhtml-customer-'] .col-system,.adminhtml-rma-item-attribute-index .col-system,[class^=' adminhtml-customer-'] .col-is_visible,.adminhtml-rma-item-attribute-index .col-is_visible,[class^=' adminhtml-customer-'] .col-sort_order,.adminhtml-rma-item-attribute-index .col-sort_order,.catalog-product-attribute-index [class^=' col-is_'],.catalog-product-attribute-index .col-required,.catalog-product-attribute-index .col-system,.adminhtml-test-index .col-is_listed,[class^=' tests-report-test'] [class^=col-inv-]{width:70px}.grid .col-phone,.sales-order-view .grid .col-period,.sales-order-create-index .col-phone,[class^=' adminhtml-rma-'] .grid .col-product_sku,.adminhtml-rma-edit .col-product,.adminhtml-rma-edit .col-sku,.catalog-product-edit .ui-tabs-panel .grid .col-name,.catalog-product-edit .ui-tabs-panel .grid .col-type,.catalog-product-edit .ui-tabs-panel .grid .col-sku,.customer-index-index .grid .col-customer_since,.customer-index-index .grid .col-billing_country_id,[class^=' customer-index-'] .fieldset-wrapper .grid .col-created_at,[class^=' customer-index-'] .accordion .grid .col-created_at{max-width:70px;width:70px}.sales-order-view .grid .col-name,.sales-order-create-index .data-table .col-product,[class^=' adminhtml-rma-'] .grid .col-name,[class^=' adminhtml-rma-'] .grid .col-product,[class^=' catalog-search'] .col-search_query,[class^=' catalog-search'] .col-synonym_for,[class^=' catalog-search'] .col-redirect,.adminhtml-urlrewrite-index .col-request_path,.reports-report-shopcart-abandoned .grid .col-name,.tax-rule-index .grid .col-title,.adminhtml-rma-item-attribute-index .grid .col-attr-code,.dashboard-secondary .dashboard-item tr>td:first-child{max-width:150px;width:150px}[class^=' sales-order-'] .grid .col-name,.catalog-category-edit .grid .col-name,.adminhtml-catalog-event-index .col-category,.adminhtml-banner-edit .grid .col-name,.reports-report-product-lowstock .grid .col-sku,.newsletter-problem-index .grid .col-name,.newsletter-problem-index .grid .col-subject,.newsletter-problem-index .grid .col-product,.adminhtml-rma-item-attribute-index .grid .col-label,.adminhtml-export-index .col-label,.adminhtml-export-index .col-code,.adminhtml-scheduled-operation-index .grid .col-name,.adminhtml-logging-index .grid .col-fullaction,.test-report-customer-wishlist-wishlist .grid .col-name,.test-report-customer-wishlist-wishlist .grid .col-subject,.test-report-customer-wishlist-wishlist .grid .col-product{max-width:220px;width:220px}.grid .col-period,.grid .col-date,.grid .col-date_to,.grid .col-date_from,.grid .col-ended_at,.grid .col-created_at,.grid .col-updated_at,.grid .col-customer_since,.grid .col-session_start_time,.grid .col-last_activity,.grid .col-email,.grid .col-items_total,.grid .col-firstname,.grid .col-lastname,.grid .col-status-default,.grid .col-websites,.grid .col-time,.grid .col-billing_name,.grid .col-shipping_name,.sales-order-index .grid .col-name,.product-options .grouped-items-table .col-name,.product-options .grouped-items-table .col-sku,[class^=' sales-order-view'] .grid .col-customer_name,[class^=' adminhtml-rma-'] .grid .col-product_name,.catalog-product-index .grid .col-name,.catalog-product-review-index .grid .col-name,.catalog-product-review-index .grid .col-title,.customer-index-edit .ui-tabs-panel .grid .col-name,.review-product-index .grid .col-name,.adminhtml-cms-page-index .col-title,.adminhtml-cms-page-index .col-identifier,.catalog-product-attribute-index .col-attr-code,.catalog-product-attribute-index .col-label,.adminhtml-logging-index .grid .col-info{max-width:110px;width:110px}.grid .col-name,.grid .col-product,.col-banner_name,.adminhtml-widget-instance-index .col-title,[class^=' adminhtml-customer-'] .col-label,.adminhtml-rma-item-attribute-index .col-label,.adminhtml-system-variable-index .grid .col-code,.ui-tabs-panel .dashboard-data .col-name,.adminhtml-test-index .col-label{max-width:370px;width:370px}.col-grid_segment_name,.reports-index-search .col-query_text{max-width:570px;width:570px}[class^=' adminhtml-rma-'] .fieldset-wrapper .data-table td,.reports-report-product-lowstock .grid .col-name,.reports-report-shopcart-product .grid .col-name,.reports-report-review-customer .grid .col-name,[class^=' adminhtml-rma-'] .fieldset-wrapper .accordion .config .data-table td{max-width:670px;width:670px}.reports-report-sales-invoiced .grid .col-period,.reports-report-sales-refunde .grid .col-period,[class^=' tests-report-test'] .grid .col-period{width:auto}.grid .col-select,.grid .col-id,.grid .col-number{width:40px}.sales-order-create-index .grid,.sales-order-create-index .grid-actions,.adminhtml-export-index .grid-actions,.adminhtml-export-index .grid{padding-left:0;padding-right:0}[class^=' adminhtml-rma-'] .col-actions a,[class^=' customer-index-'] .col-action a,.adminhtml-notification-index .col-actions a{display:block;margin:0 0 3px;white-space:nowrap}.data-table-td-max .accordion .config .data-table td,.order-account-information .data-table td,[class^=' adminhtml-rma-'] .rma-request-details .data-table td{max-width:250px;width:250px}.catalog-product-edit .ui-tabs-panel .grid .hor-scroll,.catalog-product-index .grid .hor-scroll,.review-product-index .grid .hor-scroll,.adminhtml-rma-edit .hor-scroll{overflow-x:auto}.add-clearer:after,.massaction:after,.navigation>ul:after{content:"";display:table;clear:both}.test-content{width:calc(20px + 100*0.2)}.test-content:before{content:'.test {\A ' attr(data-attribute) ': 0.2em;' '\A content:\'';white-space:pre}.test-content:after{content:' Test\';\A}' "\A" '\A.test + .test._other ~ ul > li' " {\A height: @var;\A content: ' + ';\A}";white-space:pre}.test-content-calc{width:calc((100%/12*2) - 10px)}.test-svg-xml-image{background:url('data:image/svg+xml;utf8,') no-repeat left center} \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php b/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php new file mode 100644 index 000000000000..5132a2f9456d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/Block/FormTest.php @@ -0,0 +1,86 @@ +objectManager = $this->getObjectManager(); + + parent::setUp(); + } + + /** + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Review/_files/config.php + * @dataProvider getCorrectFlagDataProvider + */ + public function testGetCorrectFlag( + $path, + $scope, + $scopeId, + $value, + $expectedResult + ) { + /** @var State $appState */ + $appState = $this->objectManager->get(State::class); + $appState->setAreaCode(Area::AREA_FRONTEND); + + /** @var Value $config */ + $config = $this->objectManager->create(Value::class); + $config->setPath($path); + $config->setScope($scope); + $config->setScopeId($scopeId); + $config->setValue($value); + $config->save(); + /** @var ReinitableConfig $reinitableConfig */ + $reinitableConfig = $this->objectManager->create(ReinitableConfig::class); + $reinitableConfig->reinit(); + + /** @var \Magento\Review\Block\Form $form */ + $form = $this->objectManager->create(\Magento\Review\Block\Form::class); + $result = $form->getAllowWriteReviewFlag(); + $this->assertEquals($result, $expectedResult); + } + + public function getCorrectFlagDataProvider() + { + return [ + [ + 'path' => 'catalog/review/allow_guest', + 'scope' => 'websites', + 'scopeId' => '1', + 'value' => 0, + 'expectedResult' => false, + ], + [ + 'path' => 'catalog/review/allow_guest', + 'scope' => 'websites', + 'scopeId' => '1', + 'value' => 1, + 'expectedResult' => true + ] + ]; + } + + private function getObjectManager() + { + return \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Review/_files/config.php b/dev/tests/integration/testsuite/Magento/Review/_files/config.php new file mode 100644 index 000000000000..13436974d630 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Review/_files/config.php @@ -0,0 +1,15 @@ +create(Value::class); +$config->setPath('catalog/review/allow_guest'); +$config->setScope('default'); +$config->setScopeId(0); +$config->setValue(1); +$config->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_customer.php b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_customer.php index b9a1a5bb5c5d..3f4db5072c42 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_customer.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_customer.php @@ -13,8 +13,12 @@ /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customer */ $customerRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() ->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); -$customer = $customerRepository->getById(1); +$customerId = 1; +$customer = $customerRepository->getById($customerId); $quote->setCustomer($customer)->setCustomerIsGuest(false)->save(); +foreach ($quote->getAllAddresses() as $address) { + $address->setCustomerId($customerId)->save(); +} /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php index d295bb89579e..4ff3dfde2417 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/I18nCollectPhrasesCommandTest.php @@ -35,7 +35,6 @@ public function tearDown() public function testExecuteConsoleOutput() { - $this->markTestSkipped('MAGETWO-64249: Unexpected test exit on Travis CI'); $this->tester->execute( [ 'directory' => BP . '/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/', diff --git a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php index 9d1ad1095b11..62b97e54bf0f 100644 --- a/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Sitemap/Model/ResourceModel/Catalog/ProductTest.php @@ -9,7 +9,7 @@ * Test class for \Magento\Sitemap\Model\ResourceModel\Catalog\Product. * - test products collection generation for sitemap * - * @magentoDataFixtureBeforeTransaction Magento/CatalogSearch/_files/full_reindex.php + * @magentoDataFixtureBeforeTransaction Magento/Catalog/_files/enable_reindex_schedule.php * @magentoDataFixture Magento/Sitemap/_files/sitemap_products.php */ class ProductTest extends \PHPUnit_Framework_TestCase diff --git a/dev/tests/integration/testsuite/Magento/Store/Block/SwitcherTest.php b/dev/tests/integration/testsuite/Magento/Store/Block/SwitcherTest.php new file mode 100644 index 000000000000..d67825ea6ec8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/Block/SwitcherTest.php @@ -0,0 +1,46 @@ +_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + } + + /** + * Test that GetTargetStorePostData() method return correct store URL. + * + * @magentoDataFixture Magento/Store/_files/store.php + * @return void + */ + public function testGetTargetStorePostData() + { + $storeCode = 'test'; + /** @var \Magento\Store\Block\Switcher $block */ + $block = $this->_objectManager->create(\Magento\Store\Block\Switcher::class); + /** @var \Magento\Store\Api\StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->_objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); + $store = $storeRepository->get($storeCode); + $result = json_decode($block->getTargetStorePostData($store), true); + + $this->assertContains($storeCode, $result['action']); + } +} diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/paypal/button.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/paypal/button.test.js new file mode 100644 index 000000000000..a4767fb551ee --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/paypal/button.test.js @@ -0,0 +1,85 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable max-nested-callbacks */ +define([ + 'squire', + 'jquery' +], function (Squire) { + 'use strict'; + + describe('Magento_Braintree/js/paypal/button', function () { + var injector, + mocks, + braintree, + component, + registry, + btnId = 'braintree_paypal_btn', + tplElement = jQuery('')[0]; + + require.config({ + map: { + '*': { + 'braintree': 'braintree' + } + } + }); + + injector = new Squire(); + mocks = { + 'braintree': { + paypal: { + /** Stub */ + initAuthFlow: function () {} + }, + + /** Stub */ + setup: function () {} + } + }; + + beforeEach(function (done) { + injector.mock(mocks); + + injector.require([ + 'braintree', + 'uiRegistry', + 'Magento_Braintree/js/paypal/button' + ], function (adapter, reg, Constr) { + braintree = adapter; + registry = reg; + jQuery(document.body).append(tplElement); + + spyOn(braintree, 'setup').and.callFake(function () { + registry.set('braintreePaypal.currentIntegration', braintree); + jQuery('#' + btnId).removeAttr('disabled'); + }); + + component = new Constr({ + id: btnId + }); + done(); + }); + }); + + afterAll(function (done) { + tplElement.remove(); + registry.remove(component.integrationName); + done(); + }); + + it('The PayPal::initAuthFlow throws an exception.', function () { + var $selector = jQuery('#' + component.id); + + spyOn(braintree.paypal, 'initAuthFlow').and.callFake(function () { + throw new TypeError('Cannot read property of undefined'); + }); + + $selector.trigger('click'); + + expect($selector.prop('disabled')).toEqual(true); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js new file mode 100644 index 000000000000..a9987f5e01ba --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js @@ -0,0 +1,79 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable max-nested-callbacks */ +define([ + 'squire', + 'ko' +], function (Squire, ko) { + 'use strict'; + + describe('Magento_Braintree/js/view/payment/method-renderer/paypal', function () { + + var injector = new Squire(), + mocks = { + 'Magento_Checkout/js/model/quote': { + billingAddress: ko.observable(), + shippingAddress: ko.observable({ + postcode: '', + street: [], + canUseForBilling: ko.observable() + }), + paymentMethod: ko.observable(), + totals: ko.observable({ + 'base_grand_total': 0 + }) + }, + 'Magento_Braintree/js/view/payment/adapter': { + checkout: { + paypal: { + /** Stub */ + initAuthFlow: function () {} + } + } + } + }, + braintreeAdapter, + component, + additionalValidator; + + beforeEach(function (done) { + window.checkoutConfig = { + quoteData: {}, + payment: { + 'braintree_paypal': { + title: 'Braintree PayPal' + } + }, + vault: {} + }; + + injector.mock(mocks); + + injector.require([ + 'Magento_Braintree/js/view/payment/adapter', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Magento_Braintree/js/view/payment/method-renderer/paypal' + ], function (adapter, validator, Constr) { + braintreeAdapter = adapter; + additionalValidator = validator; + component = new Constr(); + done(); + }); + }); + + it('The PayPal::initAuthFlow throws an exception.', function () { + + spyOn(additionalValidator, 'validate').and.returnValue(true); + spyOn(braintreeAdapter.checkout.paypal, 'initAuthFlow').and.callFake(function () { + throw new TypeError('Cannot read property of undefined'); + }); + spyOn(component.messageContainer, 'addErrorMessage'); + + component.payWithPayPal(); + expect(component.messageContainer.addErrorMessage).toHaveBeenCalled(); + }); + }); +}); diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php index 9a8982850ee2..96c5f489ee51 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php @@ -290,7 +290,12 @@ public function testClassReferences() function ($file) { $relativePath = str_replace(BP, "", $file); // Due to the examples given with the regex patterns, we skip this test file itself - if ($relativePath == "/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php") { + if ( + preg_match( + '/\/dev\/tests\/static\/testsuite\/Magento\/Test\/Integrity\/ClassesTest.php$/', + $relativePath + ) + ) { return; } $contents = file_get_contents($file); @@ -601,7 +606,7 @@ public function testCoversAnnotation() $errors = []; $filesToTest = $files->getPhpFiles(Files::INCLUDE_TESTS); - if (($key = array_search(__FILE__, $filesToTest)) !== false) { + if (($key = array_search(str_replace('\\', '/', __FILE__), $filesToTest)) !== false) { unset($filesToTest[$key]); } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerLockTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerLockTest.php index 001d7b1327dc..c85629610209 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerLockTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerLockTest.php @@ -10,15 +10,121 @@ */ class ComposerLockTest extends \PHPUnit_Framework_TestCase { - public function testUpToDate() + /** + * @return string + */ + public function testLockFileExists() { - $hash = hash_file('md5', BP . '/composer.json'); $lockFilePath = BP . '/composer.lock'; - if (!file_exists($lockFilePath)) { - $this->markTestSkipped('composer.lock file doesn\'t exist'); + $this->assertLockFileExists($lockFilePath); + return $lockFilePath; + } + + /** + * @depends testLockFileExists + * @param string $lockFilePath + * @return string + */ + public function testLockFileReadable($lockFilePath) + { + $this->assertLockFileReadable($lockFilePath); + return $lockFilePath; + } + + /** + * @depends testLockFileReadable + * @param string $lockFilePath + * @return string + */ + public function testLockFileContainsJson($lockFilePath) + { + $lockFileContent = file_get_contents($lockFilePath); + $this->assertLockFileContainsValidJson($lockFileContent); + return $lockFileContent; + } + + /** + * @depends testLockFileContainsJson + * @param string $lockFileContent + */ + public function testUpToDate($lockFileContent) + { + $lockData = json_decode($lockFileContent, true); + $composerFilePath = BP . '/composer.json'; + $this->assertLockDataRelevantToComposerFile($lockData, $composerFilePath); + } + + /** + * @param string $lockFilePath + */ + private function assertLockFileExists($lockFilePath) + { + $this->assertFileExists($lockFilePath, 'composer.lock file does not exist'); + } + + /** + * @param string $lockFilePath + */ + private function assertLockFileReadable($lockFilePath) + { + if (!is_readable($lockFilePath)) { + $this->fail('composer.lock file is not readable'); } - $jsonData = file_get_contents($lockFilePath); - $json = json_decode($jsonData); - $this->assertSame($hash, $json->hash, 'composer.lock file is not up to date'); + } + + /** + * @param string $lockFileContent + */ + private function assertLockFileContainsValidJson($lockFileContent) + { + $this->assertJson($lockFileContent, 'composer.lock file does not contains valid json'); + } + + /** + * @param array $lockData + * @param string $composerFilePath + */ + private function assertLockDataRelevantToComposerFile(array $lockData, $composerFilePath) + { + if (isset($lockData['content-hash'])) { + $this->assertLockDataRelevantToMeaningfulComposerConfig($lockData, $composerFilePath); + } else if (isset($lockData['hash'])) { + $this->assertLockDataRelevantToFullComposerConfig($lockData, $composerFilePath); + } else { + $this->fail('composer.lock does not linked to composer.json data'); + } + } + + /** + * @param array $lockData + * @param string $composerFilePath + */ + private function assertLockDataRelevantToMeaningfulComposerConfig(array $lockData, $composerFilePath) + { + $contentHashCalculator = 'Composer\Package\Locker::getContentHash'; + if (!is_callable($contentHashCalculator)) { + $this->markTestSkipped('Unable to check composer.lock file by content hash'); + } + + $composerContentHash = call_user_func($contentHashCalculator, file_get_contents($composerFilePath)); + $this->assertSame( + $composerContentHash, + $lockData['content-hash'], + 'composer.lock file is not up to date (composer.json file was modified)' + ); + } + + /** + * @param array $lockData + * @param string $composerFilePath + */ + private function assertLockDataRelevantToFullComposerConfig(array $lockData, $composerFilePath) + { + $composerFileHash = hash_file('md5', $composerFilePath); + $this->assertSame( + $composerFileHash, + $lockData['hash'], + 'composer.lock file is not up to date (composer.json file was modified)' + ); } } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php index e6a036487fc5..04e440154e96 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/ComposerTest.php @@ -99,7 +99,12 @@ private function validateComposerJsonFile($path) /** @var \Magento\Framework\Composer\MagentoComposerApplicationFactory $appFactory */ $appFactory = self::$objectManager->get(\Magento\Framework\Composer\MagentoComposerApplicationFactory::class); $app = $appFactory->create(); - $app->runComposerCommand(['command' => 'validate'], $path); + + try { + $app->runComposerCommand(['command' => 'validate'], $path); + } catch (\RuntimeException $exception) { + $this->fail($exception->getMessage()); + } } /** diff --git a/dev/travis/before_install.sh b/dev/travis/before_install.sh index 5cf8c38cf16c..63e1059f18ab 100755 --- a/dev/travis/before_install.sh +++ b/dev/travis/before_install.sh @@ -21,7 +21,7 @@ phpenv rehash; test -n "$GITHUB_TOKEN" && composer config github-oauth.github.com "$GITHUB_TOKEN" || true # Node.js setup via NVM -if [ $TEST_SUITE = "static" ] || [ test $TEST_SUITE == "js" ]; then +if [ test $TEST_SUITE == "js" ]; then curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | bash export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" # This loads nvm diff --git a/dev/travis/before_script.sh b/dev/travis/before_script.sh index 2576be6e1409..7f6ef4f91ff4 100755 --- a/dev/travis/before_script.sh +++ b/dev/travis/before_script.sh @@ -75,22 +75,26 @@ case $TEST_SUITE in cat "$changed_files_ce" | sed 's/^/ + including /' cd ../../.. - - cp package.json.sample package.json - cp Gruntfile.js.sample Gruntfile.js - yarn ;; js) cp package.json.sample package.json cp Gruntfile.js.sample Gruntfile.js yarn - echo "Installing Magento" - mysql -uroot -e 'CREATE DATABASE magento2;' - php bin/magento setup:install -q --admin-user="admin" --admin-password="123123q" --admin-email="admin@example.com" --admin-firstname="John" --admin-lastname="Doe" - - echo "Deploying Static Content" - php bin/magento setup:static-content:deploy -f -q -j=2 --no-css --no-less --no-images --no-fonts --no-misc --no-html-minify + if [[ $GRUNT_COMMAND != "static" ]]; then + echo "Installing Magento" + mysql -uroot -e 'CREATE DATABASE magento2;' + php bin/magento setup:install -q \ + --admin-user="admin" \ + --admin-password="123123q" \ + --admin-email="admin@example.com" \ + --admin-firstname="John" \ + --admin-lastname="Doe" + + echo "Deploying Static Content" + php bin/magento setup:static-content:deploy -f -q -j=2 \ + --no-css --no-less --no-images --no-fonts --no-misc --no-html-minify + fi ;; functional) echo "Installing Magento" @@ -117,7 +121,9 @@ case $TEST_SUITE in composer install && composer require se/selenium-server-standalone:2.53.1 export DISPLAY=:1.0 - sh ./vendor/se/selenium-server-standalone/bin/selenium-server-standalone -port 4444 -host 127.0.0.1 -Dwebdriver.firefox.bin=$(which firefox) -trustAllSSLCertificate &> ~/selenium.log & + sh ./vendor/se/selenium-server-standalone/bin/selenium-server-standalone -port 4444 -host 127.0.0.1 \ + -Dwebdriver.firefox.bin=$(which firefox) -trustAllSSLCertificate &> ~/selenium.log & + cp ./phpunit.xml.dist ./phpunit.xml sed -e "s?127.0.0.1?${MAGENTO_HOST_NAME}?g" --in-place ./phpunit.xml sed -e "s?basic?travis_acceptance_${ACCEPTANCE_INDEX}?g" --in-place ./phpunit.xml diff --git a/dev/travis/script.sh b/dev/travis/script.sh deleted file mode 100755 index 9df242a367ba..000000000000 --- a/dev/travis/script.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -# Copyright © Magento, Inc. All rights reserved. -# See COPYING.txt for license details. - -case $TEST_SUITE in - static) - TEST_FILTER='--filter "Magento\\Test\\Php\\LiveCodeTest"' || true - phpunit -c dev/tests/$TEST_SUITE $TEST_FILTER - grunt static - ;; - js) - grunt spec - ;; - functional) - cd dev/tests/functional - vendor/phpunit/phpunit/phpunit -c phpunit.xml --debug testsuites/Magento/Mtf/TestSuite/InjectableTests.php - ;; - *) - phpunit -c dev/tests/$TEST_SUITE $TEST_FILTER; - ;; -esac diff --git a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php index 8a69f7123781..85a6d6f570d5 100644 --- a/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php +++ b/lib/internal/Magento/Framework/Api/SearchCriteria/CollectionProcessor/SortingProcessor.php @@ -74,10 +74,12 @@ private function applyOrders(array $sortOrders, AbstractDb $collection) /** @var SortOrder $sortOrder */ foreach ($sortOrders as $sortOrder) { $field = $this->getFieldMapping($sortOrder->getField()); - $order = $sortOrder->getDirection() == SortOrder::SORT_ASC - ? Collection::SORT_ORDER_ASC - : Collection::SORT_ORDER_DESC; - $collection->addOrder($field, $order); + if (null !== $field) { + $order = $sortOrder->getDirection() == SortOrder::SORT_ASC + ? Collection::SORT_ORDER_ASC + : Collection::SORT_ORDER_DESC; + $collection->addOrder($field, $order); + } } } @@ -91,10 +93,12 @@ private function applyDefaultOrders(AbstractDb $collection) { foreach ($this->defaultOrders as $field => $direction) { $field = $this->getFieldMapping($field); - $order = $direction == SortOrder::SORT_ASC - ? Collection::SORT_ORDER_ASC - : Collection::SORT_ORDER_DESC; - $collection->addOrder($field, $order); + if (null !== $field) { + $order = $direction == SortOrder::SORT_ASC + ? Collection::SORT_ORDER_ASC + : Collection::SORT_ORDER_DESC; + $collection->addOrder($field, $order); + } } } } diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php index 27a247107fc3..fb8d3e3f28c3 100644 --- a/lib/internal/Magento/Framework/Console/Cli.php +++ b/lib/internal/Magento/Framework/Console/Cli.php @@ -11,7 +11,7 @@ use Magento\Framework\App\ProductMetadata; use Magento\Framework\App\State; use Magento\Framework\Composer\ComposerJsonFinder; -use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Console\Exception\GenerationDirectoryAccessException; use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Shell\ComplexParameter; @@ -66,17 +66,27 @@ class Cli extends Console\Application /** * @param string $name the application name * @param string $version the application version + * @SuppressWarnings(PHPMD.ExitExpression) */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { - $configuration = require BP . '/setup/config/application.config.php'; - $bootstrapApplication = new Application(); - $application = $bootstrapApplication->bootstrap($configuration); - $this->serviceManager = $application->getServiceManager(); + try { + $configuration = require BP . '/setup/config/application.config.php'; + $bootstrapApplication = new Application(); + $application = $bootstrapApplication->bootstrap($configuration); + $this->serviceManager = $application->getServiceManager(); + + $this->assertCompilerPreparation(); + $this->initObjectManager(); + $this->assertGenerationPermissions(); + } catch (\Exception $exception) { + $output = new \Symfony\Component\Console\Output\ConsoleOutput(); + $output->writeln( + '' . $exception->getMessage() . '' + ); - $this->assertCompilerPreparation(); - $this->initObjectManager(); - $this->assertGenerationPermissions(); + exit(static::RETURN_FAILURE); + } if ($version == 'UNKNOWN') { $directoryList = new DirectoryList(BP); @@ -91,20 +101,13 @@ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') /** * {@inheritdoc} * - * @throws \Exception the exception in case of unexpected error + * @throws \Exception The exception in case of unexpected error */ public function doRun(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $exitCode = parent::doRun($input, $output); if ($this->initException) { - $output->writeln( - "We're sorry, an error occurred. Try clearing the cache and code generation directories. " - . "By default, they are: " . $this->getDefaultDirectoryPath(DirectoryList::CACHE) . ", " - . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_METADATA) . ", " - . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_CODE) . ", and var/page_cache." - ); - throw $this->initException; } @@ -154,24 +157,17 @@ protected function getApplicationCommands() * Object Manager initialization. * * @return void - * @SuppressWarnings(PHPMD.ExitExpression) */ private function initObjectManager() { - try { - $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER); - $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; - - $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager(); + $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER); + $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; - /** @var ObjectManagerProvider $omProvider */ - $omProvider = $this->serviceManager->get(ObjectManagerProvider::class); - $omProvider->setObjectManager($this->objectManager); - } catch (FileSystemException $exception) { - $this->writeGenerationDirectoryReadError(); + $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager(); - exit(static::RETURN_FAILURE); - } + /** @var ObjectManagerProvider $omProvider */ + $omProvider = $this->serviceManager->get(ObjectManagerProvider::class); + $omProvider->setObjectManager($this->objectManager); } /** @@ -182,7 +178,7 @@ private function initObjectManager() * developer - application will be terminated * * @return void - * @SuppressWarnings(PHPMD.ExitExpression) + * @throws GenerationDirectoryAccessException If generation directory is read-only in developer mode */ private function assertGenerationPermissions() { @@ -197,9 +193,7 @@ private function assertGenerationPermissions() if ($state->getMode() !== State::MODE_PRODUCTION && !$generationDirectoryAccess->check() ) { - $this->writeGenerationDirectoryReadError(); - - exit(static::RETURN_FAILURE); + throw new GenerationDirectoryAccessException(); } } @@ -207,7 +201,7 @@ private function assertGenerationPermissions() * Checks whether compiler is being prepared. * * @return void - * @SuppressWarnings(PHPMD.ExitExpression) + * @throws GenerationDirectoryAccessException If generation directory is read-only */ private function assertCompilerPreparation() { @@ -222,33 +216,10 @@ private function assertCompilerPreparation() new File() ); - try { - $compilerPreparation->handleCompilerEnvironment(); - } catch (FileSystemException $e) { - $this->writeGenerationDirectoryReadError(); - - exit(static::RETURN_FAILURE); - } + $compilerPreparation->handleCompilerEnvironment(); } } - /** - * Writes read error to console. - * - * @return void - */ - private function writeGenerationDirectoryReadError() - { - $output = new \Symfony\Component\Console\Output\ConsoleOutput(); - $output->writeln( - '' - . 'Command line user does not have read and write permissions on ' - . $this->getDefaultDirectoryPath(DirectoryList::GENERATED_CODE) . ' directory. ' - . 'Please address this issue before using Magento command line.' - . '' - ); - } - /** * Retrieves vendor commands. * @@ -270,22 +241,4 @@ protected function getVendorCommands($objectManager) return $commands; } - - /** - * Get default directory path by code - * - * @param string $code - * @return string - */ - private function getDefaultDirectoryPath($code) - { - $config = DirectoryList::getDefaultConfig(); - $result = ''; - - if (isset($config[$code][DirectoryList::PATH])) { - $result = $config[$code][DirectoryList::PATH]; - } - - return $result; - } } diff --git a/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php b/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php new file mode 100644 index 000000000000..fc65cc0362e2 --- /dev/null +++ b/lib/internal/Magento/Framework/Console/Exception/GenerationDirectoryAccessException.php @@ -0,0 +1,48 @@ +getDefaultDirectoryPath(DirectoryList::GENERATED) . ' directory. ' + . 'Please address this issue before using Magento command line.' + ); + + parent::__construct($phrase, $cause, $code); + } + + /** + * Get default directory path by code + * + * @param string $code + * @return string + */ + private function getDefaultDirectoryPath($code) + { + $config = DirectoryList::getDefaultConfig(); + $result = ''; + + if (isset($config[$code][DirectoryList::PATH])) { + $result = $config[$code][DirectoryList::PATH]; + } + + return $result; + } +} diff --git a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php index 7349872ff4ac..b8978c9ef718 100644 --- a/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php +++ b/lib/internal/Magento/Framework/Console/GenerationDirectoryAccess.php @@ -7,9 +7,8 @@ use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem\Directory\WriteFactory; use Magento\Framework\Filesystem\DriverPool; -use Magento\Framework\Filesystem\File\WriteFactory; -use Magento\Framework\Filesystem\Directory\Write; use Zend\ServiceManager\ServiceManager; use Magento\Setup\Mvc\Bootstrap\InitParamListener; @@ -33,7 +32,7 @@ public function __construct( } /** - * Check generated/code read and write access + * Check write permissions to generation folders * * @return bool */ @@ -44,33 +43,32 @@ public function check() ? $initParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] : []; $directoryList = new DirectoryList(BP, $filesystemDirPaths); - $generationDirectoryPath = $directoryList->getPath(DirectoryList::GENERATED_CODE); $driverPool = new DriverPool(); $fileWriteFactory = new WriteFactory($driverPool); - /** @var \Magento\Framework\Filesystem\DriverInterface $driver */ - $driver = $driverPool->getDriver(DriverPool::FILE); - $directoryWrite = new Write($fileWriteFactory, $driver, $generationDirectoryPath); - if ($directoryWrite->isExist()) { - if ($directoryWrite->isDirectory() - || $directoryWrite->isReadable() - ) { + + $generationDirs = [ + DirectoryList::GENERATED, + DirectoryList::GENERATED_CODE, + DirectoryList::GENERATED_METADATA + ]; + + foreach ($generationDirs as $generationDirectory) { + $directoryPath = $directoryList->getPath($generationDirectory); + $directoryWrite = $fileWriteFactory->create($directoryPath); + + if (!$directoryWrite->isExist()) { try { - $probeFilePath = $generationDirectoryPath . DIRECTORY_SEPARATOR . uniqid(mt_rand()).'tmp'; - $fileWriteFactory->create($probeFilePath, DriverPool::FILE, 'w'); - $driver->deleteFile($probeFilePath); + $directoryWrite->create(); } catch (\Exception $e) { return false; } - } else { - return false; } - } else { - try { - $directoryWrite->create(); - } catch (\Exception $e) { + + if (!$directoryWrite->isWritable()) { return false; } } + return true; } } diff --git a/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php b/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php new file mode 100644 index 000000000000..fcc12c8a76bd --- /dev/null +++ b/lib/internal/Magento/Framework/Console/Test/Unit/Exception/GenerationDirectoryAccessExceptionTest.php @@ -0,0 +1,21 @@ +assertContains( + 'Command line user does not have read and write permissions on generated directory.', + $exception->getMessage() + ); + } +} diff --git a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php index 5882f8c7af6f..db466ca30e7e 100644 --- a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php +++ b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php @@ -173,12 +173,14 @@ public function write($method, $url, $http_ver = '1.1', $headers = [], $body = ' curl_setopt($this->_getResource(), CURLOPT_RETURNTRANSFER, true); if ($method == \Zend_Http_Client::POST) { curl_setopt($this->_getResource(), CURLOPT_POST, true); + curl_setopt($this->_getResource(), CURLOPT_CUSTOMREQUEST, 'POST'); curl_setopt($this->_getResource(), CURLOPT_POSTFIELDS, $body); - } elseif ($method == \Zend_Http_Client::GET) { - curl_setopt($this->_getResource(), CURLOPT_HTTPGET, true); } elseif ($method == \Zend_Http_Client::PUT) { - curl_setopt($this->_getResource(), CURLOPT_CUSTOMREQUEST, \Zend_Http_Client::PUT); + curl_setopt($this->_getResource(), CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($this->_getResource(), CURLOPT_POSTFIELDS, $body); + } elseif ($method == \Zend_Http_Client::GET) { + curl_setopt($this->_getResource(), CURLOPT_HTTPGET, true); + curl_setopt($this->_getResource(), CURLOPT_CUSTOMREQUEST, 'GET'); } if (is_array($headers)) { diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php b/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php index 449457830ef1..6b7ab238c30c 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/Redis/Config.php @@ -48,12 +48,12 @@ class Config implements \Cm\RedisSession\Handler\ConfigInterface /** * Configuration path for persistent identifier */ - const PARAM_PERSISTENT_IDENTIFIER = 'session/redis/param_persistent_identifier'; + const PARAM_PERSISTENT_IDENTIFIER = 'session/redis/persistent_identifier'; /** * Configuration path for compression threshold */ - const PARAM_COMPRESSION_THRESHOLD = 'session/redis/param_compression_threshold'; + const PARAM_COMPRESSION_THRESHOLD = 'session/redis/compression_threshold'; /** * Configuration path for compression library diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php index 7983c40b0b21..c680a24b0ee0 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php @@ -153,7 +153,7 @@ public function getDateTimeFormat($type) * {@inheritdoc} * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function date($date = null, $locale = null, $useTimezone = true) + public function date($date = null, $locale = null, $useTimezone = true, $includeTime = true) { $locale = $locale ?: $this->_localeResolver->getLocale(); $timezone = $useTimezone @@ -165,10 +165,11 @@ public function date($date = null, $locale = null, $useTimezone = true) } elseif ($date instanceof \DateTime) { return $date->setTimezone(new \DateTimeZone($timezone)); } elseif (!is_numeric($date)) { + $timeType = $includeTime ? \IntlDateFormatter::SHORT : \IntlDateFormatter::NONE; $formatter = new \IntlDateFormatter( $locale, \IntlDateFormatter::SHORT, - \IntlDateFormatter::SHORT, + $timeType, new \DateTimeZone($timezone) ); $date = $formatter->parse($date) ?: (new \DateTime($date))->getTimestamp(); diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/TimezoneInterface.php b/lib/internal/Magento/Framework/Stdlib/DateTime/TimezoneInterface.php index 1fd96eabf350..304519e3644f 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/TimezoneInterface.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/TimezoneInterface.php @@ -60,12 +60,13 @@ public function getDateTimeFormat($type); /** * Create \DateTime object for current locale * - * @param mixed $date + * @param mixed $date * @param string $locale - * @param bool $useTimezone + * @param bool $useTimezone + * @param bool $includeTime * @return \DateTime */ - public function date($date = null, $locale = null, $useTimezone = true); + public function date($date = null, $locale = null, $useTimezone = true, $includeTime = true); /** * Create \DateTime object with date converted to scope timezone and scope Locale diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php new file mode 100644 index 000000000000..77c8c5607d25 --- /dev/null +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php @@ -0,0 +1,71 @@ +objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + parent::setUp(); + } + + /** + * Test date parsing with different includeTime options + * + * @param string $date + * @param string $locale + * @param bool $includeTime + * @param int $expectedTimestamp + * @dataProvider dateIncludeTimeDataProvider + */ + public function testDateIncludeTime($date, $locale, $includeTime, $expectedTimestamp) + { + $this->scopeConfigMock->method('getValue')->willReturn('America/Chicago'); + /** @var Timezone $timezone */ + $timezone = $this->objectManager->getObject(Timezone::class, ['scopeConfig' => $this->scopeConfigMock]); + + /** @var \DateTime $dateTime */ + $dateTime = $timezone->date($date, $locale, true, $includeTime); + $this->assertEquals($expectedTimestamp, $dateTime->getTimestamp()); + } + + public function dateIncludeTimeDataProvider() + { + return [ + 'Parse date without time' => [ + '19/05/2017', // date + 'ar_KW', // locale + false, // include time + 1495170000 // expected timestamp + ], + 'Parse date with time' => [ + '05/19/2017 00:01 am', // date + 'en_US', // locale + true, // include time + 1495170060 // expected timestamp + ], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Validator/Builder.php b/lib/internal/Magento/Framework/Validator/Builder.php index b50c729c3973..41be9346251c 100644 --- a/lib/internal/Magento/Framework/Validator/Builder.php +++ b/lib/internal/Magento/Framework/Validator/Builder.php @@ -6,8 +6,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\Framework\Validator; use Magento\Framework\Validator\Constraint\OptionInterface; @@ -257,7 +255,11 @@ protected function _createConstraint(array $data) } if (\Magento\Framework\Validator\Config::CONSTRAINT_TYPE_PROPERTY == $data['type']) { - $result = new \Magento\Framework\Validator\Constraint\Property($validator, $data['property'], $data['alias']); + $result = new \Magento\Framework\Validator\Constraint\Property( + $validator, + $data['property'], + $data['alias'] + ); } else { $result = $this->_constraintFactory->create(['validator' => $validator, 'alias' => $data['alias']]); } @@ -286,7 +288,10 @@ protected function _createConstraintValidator(array $data) // Check validator type if (!$validator instanceof \Magento\Framework\Validator\ValidatorInterface) { throw new \InvalidArgumentException( - sprintf('Constraint class "%s" must implement \Magento\Framework\Validator\ValidatorInterface', $data['class']) + sprintf( + 'Constraint class "%s" must implement \Magento\Framework\Validator\ValidatorInterface', + $data['class'] + ) ); } @@ -300,8 +305,10 @@ protected function _createConstraintValidator(array $data) * @param array $options * @return void */ - protected function _configureConstraintValidator(\Magento\Framework\Validator\ValidatorInterface $validator, array $options) - { + protected function _configureConstraintValidator( + \Magento\Framework\Validator\ValidatorInterface $validator, + array $options + ) { // Call all validator methods according to configuration if (isset($options['methods'])) { foreach ($options['methods'] as $methodData) { diff --git a/lib/internal/Magento/Framework/Validator/Constraint/Property.php b/lib/internal/Magento/Framework/Validator/Constraint/Property.php index a209e00f738e..f4842e15ea32 100644 --- a/lib/internal/Magento/Framework/Validator/Constraint/Property.php +++ b/lib/internal/Magento/Framework/Validator/Constraint/Property.php @@ -6,8 +6,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - namespace Magento\Framework\Validator\Constraint; class Property extends \Magento\Framework\Validator\Constraint @@ -33,8 +31,8 @@ public function __construct(\Magento\Framework\Validator\ValidatorInterface $val } /** - * Get value that should be validated. Tries to extract value's property if \Magento\Framework\DataObject or \ArrayAccess or array - * is passed + * Get value that should be validated. Tries to extract value's property if \Magento\Framework\DataObject or + * \ArrayAccess or array is passed * * @param mixed $value * @return mixed diff --git a/lib/internal/Magento/Framework/Validator/ConstraintFactory.php b/lib/internal/Magento/Framework/Validator/ConstraintFactory.php index 43d3ef3d9cb3..d90c4ee47338 100644 --- a/lib/internal/Magento/Framework/Validator/ConstraintFactory.php +++ b/lib/internal/Magento/Framework/Validator/ConstraintFactory.php @@ -4,8 +4,6 @@ * See COPYING.txt for license details. */ -// @codingStandardsIgnoreFile - /** * Factory class for \Magento\Framework\Validator\Constraint */ @@ -33,8 +31,10 @@ class ConstraintFactory * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param string $instanceName */ - public function __construct(\Magento\Framework\ObjectManagerInterface $objectManager, $instanceName = \Magento\Framework\Validator\Constraint::class) - { + public function __construct( + \Magento\Framework\ObjectManagerInterface $objectManager, + $instanceName = \Magento\Framework\Validator\Constraint::class + ) { $this->_objectManager = $objectManager; $this->_instanceName = $instanceName; } diff --git a/lib/internal/Magento/Framework/Validator/Factory.php b/lib/internal/Magento/Framework/Validator/Factory.php index a1b1752d5ded..c7df8547fb4b 100644 --- a/lib/internal/Magento/Framework/Validator/Factory.php +++ b/lib/internal/Magento/Framework/Validator/Factory.php @@ -3,18 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\Validator; use Magento\Framework\Cache\FrontendInterface; -/** - * @codingStandardsIgnoreFile - */ class Factory { - /** - * Cache key - */ + /** cache key */ const CACHE_KEY = __CLASS__; /** @@ -73,6 +69,8 @@ public function __construct( /** * Init cached list of validation files + * + * @return void */ protected function _initializeConfigList() { @@ -80,7 +78,10 @@ protected function _initializeConfigList() $this->_configFiles = $this->cache->load(self::CACHE_KEY); if (!$this->_configFiles) { $this->_configFiles = $this->moduleReader->getConfigurationFiles('validation.xml'); - $this->cache->save($this->getSerializer()->serialize($this->_configFiles->toArray()), self::CACHE_KEY); + $this->cache->save( + $this->getSerializer()->serialize($this->_configFiles->toArray()), + self::CACHE_KEY + ); } else { $filesArray = $this->getSerializer()->unserialize($this->_configFiles); $this->_configFiles = $this->getFileIteratorFactory()->create(array_keys($filesArray)); @@ -121,7 +122,9 @@ public function getValidatorConfig() $this->_initializeConfigList(); $this->_initializeDefaultTranslator(); return $this->_objectManager->create( - \Magento\Framework\Validator\Config::class, ['configFiles' => $this->_configFiles]); + \Magento\Framework\Validator\Config::class, + ['configFiles' => $this->_configFiles] + ); } /** @@ -161,7 +164,9 @@ public function createValidator($entityName, $groupName, array $builderConfig = private function getSerializer() { if ($this->serializer === null) { - $this->serializer = $this->_objectManager->get(\Magento\Framework\Serialize\SerializerInterface::class); + $this->serializer = $this->_objectManager->get( + \Magento\Framework\Serialize\SerializerInterface::class + ); } return $this->serializer; } @@ -175,8 +180,9 @@ private function getSerializer() private function getFileIteratorFactory() { if ($this->fileIteratorFactory === null) { - $this->fileIteratorFactory = $this->_objectManager - ->get(\Magento\Framework\Config\FileIteratorFactory::class); + $this->fileIteratorFactory = $this->_objectManager->get( + \Magento\Framework\Config\FileIteratorFactory::class + ); } return $this->fileIteratorFactory; } diff --git a/lib/internal/Magento/Framework/View/Element/Html/Calendar.php b/lib/internal/Magento/Framework/View/Element/Html/Calendar.php index 8bfa611466ac..0c8187569cf2 100644 --- a/lib/internal/Magento/Framework/View/Element/Html/Calendar.php +++ b/lib/internal/Magento/Framework/View/Element/Html/Calendar.php @@ -78,14 +78,31 @@ protected function _toHtml() ] ); - // get months names + /** + * Month names in abbreviated format values was added to ICU Data tables + * starting ICU library version 52.1. For some OS, like CentOS, default + * installation version of ICU library is 50.1.2, which not contain + * 'abbreviated' key, and that may cause a PHP fatal error when passing + * as an argument of function 'iterator_to_array'. This issue affects + * locales like ja_JP, ko_KR etc. + * + * @see http://source.icu-project.org/repos/icu/tags/release-50-1-2/icu4c/source/data/locales/ja.txt + * @see http://source.icu-project.org/repos/icu/tags/release-52-1/icu4c/source/data/locales/ja.txt + * @var \ResourceBundle $monthsData + */ $monthsData = $localeData['calendar']['gregorian']['monthNames']; $this->assign( 'months', [ 'wide' => $this->encoder->encode(array_values(iterator_to_array($monthsData['format']['wide']))), 'abbreviated' => $this->encoder->encode( - array_values(iterator_to_array($monthsData['format']['abbreviated'])) + array_values( + iterator_to_array( + null !== $monthsData->get('format')->get('abbreviated') + ? $monthsData['format']['abbreviated'] + : $monthsData['format']['wide'] + ) + ) ), ] ); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/CalendarTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/CalendarTest.php index d5a9adb80a84..36e16499ca76 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/CalendarTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/CalendarTest.php @@ -5,49 +5,92 @@ */ namespace Magento\Framework\View\Test\Unit\Element\Html; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Element\Html\Calendar; +use Magento\Framework\View\Element\Template\Context; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * @see Calendar + */ class CalendarTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @see MAGETWO-60828 + * @see Calendar::_toHtml + * + * @param string $locale + * @dataProvider localesDataProvider */ - protected $objectManagerHelper; - - /** @var \Magento\Framework\View\Element\Html\Calendar */ - protected $block; - - /** @var \Magento\Framework\View\Element\Template\Context */ - protected $context; + public function testToHtmlWithDifferentLocales($locale) + { + $calendarBlock = (new ObjectManager($this))->getObject( + Calendar::class, + [ + 'localeResolver' => $this->getLocalResolver($locale) + ] + ); - /** @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $localeDate; + $calendarBlock->toHtml(); + } - protected function setUp() + /** + * @return array + */ + public function localesDataProvider() { - $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->localeDate = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class) - ->getMock(); + return [ + ['en_US'], + ['ja_JP'], + ['ko_KR'], + ]; + } - /** @var \Magento\Framework\View\Element\Template\Context $context */ - $this->context = $this->objectManagerHelper->getObject( - \Magento\Framework\View\Element\Template\Context::class, + /** + * @see Calendar::getYearRange + */ + public function testGetYearRange() + { + $calendarBlock = (new ObjectManager($this))->getObject( + Calendar::class, [ - 'localeDate' => $this->localeDate, + 'context' => $this->getContext() ] ); - /** @var \Magento\Framework\View\Element\Html\Links $block */ - $this->block = $this->objectManagerHelper->getObject( - \Magento\Framework\View\Element\Html\Calendar::class, - ['context' => $this->context] + $testCurrentYear = (new \DateTime())->format('Y'); + $this->assertEquals( + (int) $testCurrentYear - 100 . ':' . ($testCurrentYear + 100), + $calendarBlock->getYearRange() ); } /** - * @test + * @param string $locale + * @return ResolverInterface|MockObject */ - public function testGetYearRange() + private function getLocalResolver($locale) { - $testCurrentYear = (new \DateTime())->format('Y'); - $this->assertEquals((int)$testCurrentYear - 100 . ':' . ($testCurrentYear + 100), $this->block->getYearRange()); + $localResolver = $this->getMockBuilder(ResolverInterface::class) + ->getMockForAbstractClass(); + $localResolver->method('getLocale')->willReturn($locale); + + return $localResolver; + } + + /** + * @return Context|Object + */ + private function getContext() + { + $localeDate = $this->getMockBuilder(TimezoneInterface::class) + ->getMockForAbstractClass(); + + return (new ObjectManager($this))->getObject( + Context::class, + ['localeDate' => $localeDate] + ); } } diff --git a/lib/web/mage/adminhtml/form.js b/lib/web/mage/adminhtml/form.js index 59ac8f7cea6c..0053dce8207d 100644 --- a/lib/web/mage/adminhtml/form.js +++ b/lib/web/mage/adminhtml/form.js @@ -453,6 +453,10 @@ define([ if (target) { inputs = target.up(this._config['levels_up']).select('input', 'select', 'td'); isAnInputOrSelect = ['input', 'select'].indexOf(target.tagName.toLowerCase()) != -1; //eslint-disable-line + + if (target.type === 'fieldset') { + inputs = target.select('input', 'select', 'td'); + } } else { inputs = false; isAnInputOrSelect = false; @@ -531,6 +535,10 @@ define([ if (rowElement == undefined && target) { //eslint-disable-line eqeqeq rowElement = target.up(this._config['levels_up']); + + if (target.type === 'fieldset') { + rowElement = target; + } } if (rowElement) { diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js index aa0b19f5f11a..f2b3365555f3 100755 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -463,8 +463,6 @@ define([ var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)')); - content = decodeURIComponent(content); - return content.gsub(reg, function (match) { //eslint-disable-line no-extra-bind return Base64.mageDecode(match[1]); }); diff --git a/setup/src/Magento/Setup/Console/Command/DeployStaticContentCommand.php b/setup/src/Magento/Setup/Console/Command/DeployStaticContentCommand.php index d2a0ddce1b42..4217c9ea5c65 100644 --- a/setup/src/Magento/Setup/Console/Command/DeployStaticContentCommand.php +++ b/setup/src/Magento/Setup/Console/Command/DeployStaticContentCommand.php @@ -120,11 +120,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $options = $input->getOptions(); $options[Options::LANGUAGE] = $input->getArgument(Options::LANGUAGES_ARGUMENT) ?: ['all']; + $refreshOnly = isset($options[Options::REFRESH_CONTENT_VERSION_ONLY]) + && $options[Options::REFRESH_CONTENT_VERSION_ONLY]; $verbose = $output->getVerbosity() > 1 ? $output->getVerbosity() : OutputInterface::VERBOSITY_VERBOSE; $logger = $this->consoleLoggerFactory->getLogger($output, $verbose); - $logger->alert(PHP_EOL . "Deploy using {$options[Options::STRATEGY]} strategy"); + if (!$refreshOnly) { + $logger->alert(PHP_EOL . "Deploy using {$options[Options::STRATEGY]} strategy"); + } $this->mockCache(); @@ -135,7 +139,9 @@ protected function execute(InputInterface $input, OutputInterface $output) $deployService->deploy($options); - $logger->alert(PHP_EOL . "Execution time: " . (microtime(true) - $time)); + if (!$refreshOnly) { + $logger->alert(PHP_EOL . "Execution time: " . (microtime(true) - $time)); + } return \Magento\Framework\Console\Cli::RETURN_SUCCESS; } diff --git a/setup/src/Magento/Setup/Console/CompilerPreparation.php b/setup/src/Magento/Setup/Console/CompilerPreparation.php index 6bdddfe0053a..9ea938d51fb3 100644 --- a/setup/src/Magento/Setup/Console/CompilerPreparation.php +++ b/setup/src/Magento/Setup/Console/CompilerPreparation.php @@ -7,6 +7,7 @@ use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Console\Exception\GenerationDirectoryAccessException; use Magento\Framework\Console\GenerationDirectoryAccess; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem\Driver\File; @@ -59,31 +60,31 @@ public function __construct( /** * Determine whether a CLI command is for compilation, and if so, clear the directory. * - * @throws FileSystemException if generation directory is read-only + * @throws GenerationDirectoryAccessException If generation directory is read-only * @return void */ public function handleCompilerEnvironment() { - $compilationCommands = [DiCompileCommand::NAME]; + $compilationCommands = $this->getCompilerInvalidationCommands(); $cmdName = $this->input->getFirstArgument(); $isHelpOption = $this->input->hasParameterOption('--help') || $this->input->hasParameterOption('-h'); if (!in_array($cmdName, $compilationCommands) || $isHelpOption) { return; } - $compileDirList = []; + + if (!$this->getGenerationDirectoryAccess()->check()) { + throw new GenerationDirectoryAccessException(); + } + $mageInitParams = $this->serviceManager->get(InitParamListener::BOOTSTRAP_PARAM); $mageDirs = isset($mageInitParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS]) ? $mageInitParams[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS] : []; $directoryList = new DirectoryList(BP, $mageDirs); - $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_CODE); - $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATED_METADATA); - - if (!$this->getGenerationDirectoryAccess()->check()) { - throw new FileSystemException( - new Phrase('Generation directory can not be written.') - ); - } + $compileDirList = [ + $directoryList->getPath(DirectoryList::GENERATED_CODE), + $directoryList->getPath(DirectoryList::GENERATED_METADATA), + ]; foreach ($compileDirList as $compileDir) { if ($this->filesystemDriver->isExists($compileDir)) { @@ -92,6 +93,22 @@ public function handleCompilerEnvironment() } } + /** + * Retrieves command list with commands which invalidates compiler + * + * @return array + */ + private function getCompilerInvalidationCommands() + { + return [ + DiCompileCommand::NAME, + 'module:disable', + 'module:enable', + 'module:uninstall', + 'deploy:mode:set' + ]; + } + /** * Retrieves generation directory access checker. * diff --git a/setup/src/Magento/Setup/Module/Di/Compiler/Log/Log.php b/setup/src/Magento/Setup/Module/Di/Compiler/Log/Log.php index f04a78f7d957..a757cda7ba79 100644 --- a/setup/src/Magento/Setup/Module/Di/Compiler/Log/Log.php +++ b/setup/src/Magento/Setup/Module/Di/Compiler/Log/Log.php @@ -80,11 +80,17 @@ public function add($type, $key, $message = '') * Write entries * * @return void + * @throws \Magento\Framework\Validator\Exception */ public function report() { $this->_successWriter->write($this->_successEntries); $this->_errorWriter->write($this->_errorEntries); + //do not take into account empty items since they are initialized in constructor. + $errors = array_filter($this->_errorEntries); + if (count($errors) > 0) { + throw new \Magento\Framework\Validator\Exception(__('Error during compilation')); + } } /** diff --git a/setup/view/magento/setup/marketplace-credentials.phtml b/setup/view/magento/setup/marketplace-credentials.phtml index 22ad4b6558f3..c13517f2200e 100644 --- a/setup/view/magento/setup/marketplace-credentials.phtml +++ b/setup/view/magento/setup/marketplace-credentials.phtml @@ -23,6 +23,7 @@
@@ -63,6 +64,7 @@ || (auth.username.$error.required && user.submitted) }" autofocus required + autocomplete="off" >
This is a required field. @@ -84,6 +86,7 @@ && !auth.password.$pristine) || (auth.password.$error.required && user.submitted) }" required + autocomplete="new-password" >
This is a required field. diff --git a/setup/view/magento/setup/popupauth.phtml b/setup/view/magento/setup/popupauth.phtml index 87263c724746..bce55a7c336d 100644 --- a/setup/view/magento/setup/popupauth.phtml +++ b/setup/view/magento/setup/popupauth.phtml @@ -32,6 +32,7 @@
@@ -62,6 +63,7 @@ || (auth.username.$error.required && user.submitted) }" autofocus required + autocomplete="off" >
This is a required field. @@ -83,6 +85,7 @@ && !auth.password.$pristine) || (auth.password.$error.required && user.submitted) }" required + autocomplete="new-password" >
This is a required field. diff --git a/setup/view/magento/setup/system-config.phtml b/setup/view/magento/setup/system-config.phtml index 65c13ab3cdf1..55087c5a9949 100644 --- a/setup/view/magento/setup/system-config.phtml +++ b/setup/view/magento/setup/system-config.phtml @@ -57,7 +57,7 @@

Magento Marketplace

Sign in to sync your Magento Marketplace purchases.

- +
@@ -74,6 +74,7 @@ || (auth.username.$error.required && user.submitted)}" autofocus required + autocomplete="off" >
This is a required field. @@ -100,7 +101,7 @@ ng-class="{ 'invalid' : (auth.password.$error.required && !auth.password.$pristine) || (auth.password.$error.required && user.submitted) }" required - autocomplete="off" + autocomplete="new-password" >
This is a required field.